1 Introduction

1.1 What am I hoping to achieve with this project?

  • predict how long it will take to learn a piece based on various features
  • discover insights into my practice habits and find areas where I need to improve
  • develop a recommender tool of piano pieces for others to select from based on factors of interest
  • hopefully act as a source of inspiration/realistic piano progress for others who want to learn a musical instrument

Context: I started playing the piano in 2018 as a complete beginner and I’ve been tracking my practice time for around 2 and a half years. I now decided to put that to good use and see what interesting patterns I might be able to find.

1.2 Data collection

  • imputed conservative estimations for the first 10 months of the first year (Jan ’18 to Oct ’18)
  • everything from Dec ’18 onwards was tracked using Toggl, a time-tracking app/tool
  • these times include my practice sessions (usually in short bouts of 30 minutes); piano lessons are excluded (usually 2-3 hours total per month)
  • the Extract, Transform, Load script is available in the global.R file of this repo

Disclaimer: I am not affiliated with Toggl. I started using it a few years ago because it provided all the functionality I needed and loved its minimalistic design. The standard membership, which I use, is free of charge.

Key points:

  • identified various trends based on my practice
  • predicted an algorithm
  • practiced less in lockdown
  • impact of something like sight reading on learning a piece faster / technique work (which I now track)
  • testing on current pieces
knitr::opts_chunk$set(
    echo = TRUE, # show all of the code
    tidy = FALSE, # cleaner code printing
    size = "small", # smaller code
    
    fig.path = "figs/",# where the figures will end up
    out.width = "100%",

    message = FALSE,
    warning = FALSE
    )

2 Exploratory Data Analysis (EDA)

2.1 Piano practice timeline

raw_data%>%
  group_by(Month_format)%>%
  summarise(Total_Duration = sum(Duration)/60)%>%
  mutate(Total_Duration2 = as.integer(cumsum(Total_Duration)),
         max = as.integer(max(Total_Duration2)),
         max = ifelse(max > Total_Duration2, "", max))%>%

  ggplot(aes(Month_format, Total_Duration2, group = 1))+
  geom_line(size = 2, color = "#69b3a2")+
  geom_point(size = 5, color = "#69b3a2")+
  geom_area(alpha = 0.3, fill = "#69b3a2")+
  # grade 3
  geom_point(x="Oct\n '18", y = 253, size = 5, color = "dark red")+
  geom_text(x="Oct\n '18", y = 253+200, size = 5, label = "Grade 3")+
  geom_text(x="Oct\n '18", y = 253+100,  size = 5, label = "253 hours")+
  # grade 5
  geom_point(x="Oct\n '19", y = 675, size = 5, color = "dark red")+
  geom_text(x="Oct\n '19", y = 675+200,  size = 5, label = "Grade 5")+
  geom_text(x="Oct\n '19", y = 675+100,  size = 5, label = "675 hours")+
  # grade 6
  geom_point(x="Oct\n '20", y = 1078, size = 5, color = "dark red")+
  geom_text(x="Oct\n '20", y = 1078+200,  size = 5, label = "Grade 6")+
  geom_text(x="Oct\n '20", y = 1078+100,  size = 5, label = "1078 hours")+
  # NOW
  geom_point(aes(x="Apr\n '21", y = 1219), size = 5, color = "dark red")+
  geom_text(aes(label = max), nudge_y = 75, nudge_x = -0.5, size = 5)+
  scale_fill_gradient(low="yellow", high="red")+
  labs(x = NULL,
       y = "Total hours of practice",
       title = "Piano practice timeline")+
  theme_ipsum_es()+
  theme(legend.position = "top")

2.2 How long did I practice per piece?

Based on the level at the time and the difficulty of the piece, we can see that each piece took around 10-30 hours of practice.

raw_data%>%
  filter(Date_Start > as.Date("2018/11/01"))%>%
  filter(Completed == "Yes")%>%
  group_by(Project, Date_Start)%>%
  summarise(Duration = sum(Duration)/60)%>%
  mutate(Cumulative_Piece = cumsum(Duration),
         Month_Year = as.factor(as.yearmon(Date_Start)),
         Month_format = str_replace(Month_Year, " 20", "\n '"))%>%
  ungroup()%>%
  mutate(Cumulative_Total = cumsum(Duration))%>%
  filter(Project %notin% c("Technique", "General", "Sightreading"))%>%
  left_join(model_data%>%select(Level, Project, ABRSM), by = "Project")%>%
  
ggplot(aes(Date_Start, Cumulative_Piece, fill = Level)) +
  geom_point(size = 10, shape = 21, col = "black", alpha = 0.5) +
  scale_size(range = c(.1, 16), guide = FALSE) +
  labs(title = 'Year: {frame_time}',
       y = "Total practice time per piece (hours)")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es() +
  theme(legend.position = "top")+
  transition_time(Date_Start) +
  ease_aes('linear')+
  exit_fade() +
  shadow_mark(alpha = 0.1, size = 5)

# save animation as gif for later use
# anim_save("figs/timeline.gif")

2.3 How consistent was my practice?

Generally, I’ve done pretty well to maintain a high level of consistency with the exception of August/December. This is usually where I tend to take annual leave.

raw_data%>%
  filter(Source != "Estimated")%>%
  group_by(Month_Year, Month_Start, Month_format)%>%
  summarise(Days_Practice = n_distinct(Date_Start),
            Total_Duration = sum(Duration, na.rm = TRUE))%>%
  mutate(Days_Total = days_in_month(Month_Start),
         Days_Not_Practiced = Days_Total - Days_Practice,
         Avg_Duration = as.integer(Total_Duration/Days_Total),
         Consistency = round(Days_Practice / Days_Total * 100,2),
         Consistency_Status = ifelse(Consistency<75, "Bad", "Good"),
         Month_format = reorder(Month_format, Month_Year))%>%
  
  ggplot(aes(Month_format, Consistency, fill = Consistency_Status))+
  geom_col(group = 1, col = "black")+
  geom_hline(yintercept = 75, lty = "dashed")+
  geom_text(aes(label = Days_Not_Practiced), size = 5, nudge_y = 3)+
  labs(x = NULL,
       fill = "Consistency status",
       subtitle = "Numbers indicate days without any practice within each month")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "top")

2.4 Was there a trend in my amount of daily average practice?

We can see that my practice time was correlated with the consistency, where the average session was much shorter in the months I was away from the piano. There’s also a trend where my practice close to an exam session was significantly higher than any other time of the year. Can you spot in which month I had my exam in 2019? What about the end of 2020?

average practice length per month includes the days in which I did not practice

overall

raw_data%>%
  filter(Source != "Estimated")%>%
  group_by(Month_Year, Month_Start, Month_format)%>%
  summarise(Days_Practice = n_distinct(Date_Start),
            Total_Duration = sum(Duration))%>%
  mutate(Days_Total = days_in_month(Month_Start),
         Avg_Duration = as.integer(Total_Duration/Days_Total),
         Avg_Duration_Status = ifelse(Avg_Duration < 60, "Less than one hour", "One hour"),
         Month_format = reorder(Month_format, Month_Year))%>%
  
  ggplot(aes(Month_format, Avg_Duration, fill = Avg_Duration_Status))+
  geom_col(col = "black")+
  labs(x = NULL,
       y = "Average practice session length (min)",
       fill = "Status")+
  geom_text(aes(label = Avg_Duration), nudge_y = 5, size = 5)+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "top",
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank())

Year on Year

Similar trends as before are apparent where my average daily session is longer before the exams than any other time in the year and a dip in the months where I usually take most of my annual leave. I really do need to start picking up the pace and get back to where I used to be.

raw_data%>%
  group_by(Month_Year, Month_Start, Month_format, Month_Name, Year)%>%
  summarise(Days_Practice = n_distinct(Date_Start),
            Total_Duration = sum(Duration))%>%
  mutate(Days_Total = days_in_month(Month_Start),
         Avg_Duration = as.integer(Total_Duration/Days_Total),
         Avg_Duration_Status = ifelse(Avg_Duration < 60, "Less than one hour", "One hour"),
         Month_format = reorder(Month_format, Month_Year),
         size = as.factor(ifelse(Year == 2018, 1, 1.5)),
         label = ifelse(month(Month_Start) == 1, as.character(Year), ""))%>%
  
  ggplot(aes(Month_Name, Avg_Duration, group = Year, size = size))+
  geom_line(aes(col = Year))+
  geom_label_repel(aes(label = label, col = Year))+
  labs(x = NULL,
       fill = "Status")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "none")

2.5 Did COVID significantly impact my practice time?

graph

Despite a similar median, we can see that the practice sessions were less likely to be over 80 min after COVID. We can test if this was a significant impact with a t-test.

covid_start <- as.Date("2020/03/23")

inference <- raw_data%>%
  filter(Source != "Estimated")%>%
  mutate(Covid_Status = as.factor(ifelse(Date_Start < covid_start, "Before COVID", "After COVID")),
         Covid_Status = reorder(Covid_Status, desc(Covid_Status)))%>%
  group_by(Covid_Status, Date_Start)%>%
  summarise(Duration = sum(Duration))%>%
  ungroup()
  
  ggplot(inference, aes(Covid_Status, Duration, fill = Covid_Status))+
  geom_boxplot(varwidth = TRUE, col = "black")+
  labs(x = NULL,
       y = "Average practice session (min)")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "none")

skewness assumption

Given the extremely low p-value, the Shapiro-Wilk normality test implies that the distribution of the data is significantly different from a normal distribution and that we cannot assume the normality. However, we’re working with the entire population dataset for each class and thus, unlike the independence of data, this assumption is not crucial.

  inference %>% 
  select(Covid_Status, Duration) %>% 
  group_by(group = as.character(Covid_Status)) %>%
  do(tidy(shapiro.test(.$Duration)))%>%
  kbl(caption = "Shapiro-Wilk normality test")%>%
  kable_paper("hover", full_width = F)
Shapiro-Wilk normality test
group statistic p.value method
After COVID 0.9607325 3e-07 Shapiro-Wilk normality test
Before COVID 0.9549818 0e+00 Shapiro-Wilk normality test

equal variances assumption

We can see that with a large p value, we should fail to reject the Null hypothesis (Ho) and conclude that we do not have evidence to believe that population variances are not equal and use the equal variances assumption for our t test

tidy(leveneTest(inference$Duration~inference$Covid_Status))%>%
  kbl(caption = "Levene's test")%>%
  kable_paper("hover", full_width = F)
Levene’s test
statistic p.value df df.residual
0.0410026 0.8395891 1 732

t-test

My practice sessions post-COVID are significantly shorter than those before the pandemic. This might be surprising, given that we were in the lockdown most of the time. However, I’ve been spending my time doing a few other things such as improving my technical skillset with R (this analysis wouldn’t have been possible otherwise) and learning italian.

t_test <- inference%>%
  t_test(Duration ~ Covid_Status, var.equal = TRUE)%>%
  add_significance()%>%
  kbl()%>%
  kable_paper("hover", full_width = F)

t_test
.y. group1 group2 n1 n2 statistic df p p.signif
Duration Before COVID After COVID 433 301 3.319481 732 0.000947 ***

2.6 What type of music do I tend to play?

by genre

graph_practice <- function(variable, nudge){
  raw_data%>%
  filter(Genre %notin% c("Other", "Not applicable"))%>%
  group_by({{variable}})%>%
  summarise(Duration = as.integer(sum(Duration)/60))%>%
  arrange(desc(Duration))%>%
  head(10)%>%

  ggplot(aes(reorder({{variable}}, Duration), Duration, fill = Duration))+
  geom_col(show.legend = FALSE, col = "black", width = 1)+
  geom_text(aes(label = Duration), show.legend = FALSE, nudge_y = nudge, size = 5)+
  scale_fill_gradient(low="yellow", high="red")+
  labs(x = NULL,
       y = "Total hours of practice")+
  coord_flip()+
  theme_ipsum_es()+
  theme(axis.text.x = element_blank(),
        axis.ticks = element_blank())
}

graph_practice(Genre, 15)

by composer

graph_practice(Composer, 5)

by piece

graph_practice(Project, 3)

2.7 Relation between difficulty and number of practice hours

ABRSM grade

Simplified, ABBRSM grades are a group of 8 exams based on their difficulty (1 - beginner to 8 - advanced). There are also diploma grades but those are extremely advanced, equivalent to university level studies and out of the scope of this analysis.

More information can be found on their official website at https://gb.abrsm.org/en/exam-support/your-guide-to-abrsm-exams/

model_data%>%
  mutate(Duration = Duration)%>%
  
  ggplot(aes(ABRSM, Duration, fill = ABRSM))+
  geom_boxplot(varwidth = TRUE, outlier.colour = "red")+
  labs(x = "ABRSM Grade",
       y = "Total practice hours",
       subtitle = "The higher the difficulty, the more time it takes to learn a piece")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "none")

level

A further aggregation of ABRSM grades; this is helpful given the very limited dataset within each grade and much easier on the eye. This is an oversimplification but they’re classified as: * 1-5: Beginner (1) * 5-6: Intermediate (2) * 7-8: Advanced (3)

model_data%>%
  mutate(Duration = Duration)%>%
  
  ggplot(aes(Level, Duration, fill = Level))+
  geom_boxplot(varwidth = TRUE, outlier.colour = "red")+
  scale_color_tron()+
  scale_fill_tron()+
  labs(x = "Level",
       y = "Total practice hours",
       subtitle = "The higher the difficulty, the more time it takes to learn a piece")+
  theme_ipsum_es()+
  theme(legend.position = "none")

2.8 What about the piece length?

model_data%>%
  
  ggplot(aes(Length, Duration, group = 1))+
  geom_jitter(aes(col = Level), width = 0.5, height = 0.5, size = 3)+
  geom_smooth(method = "lm", se=FALSE)+
  labs(x = "Piece length (mins)",
       y = "Hours needed to learn a piece",
       subtitle = "There appears to be a linear trend between piece length and total practice time")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "top")

2.9 Learning effect - do pieces of the same difficulty become easier to learn with time?

We can spot a trend where the time required to learn a piece of a similar difficulty (ABRSM Grade) decreases as my ability to play the piano increases (as judged by cumulative hours of practice). We should keep this in mind and include it as a variable into our prediction model.

model_data%>%
  
  ggplot(aes(Cumulative_Duration, Duration, group = 1))+
  geom_point(aes(col = Level), size = 3)+
  geom_smooth(method = "lm", se=FALSE)+
  labs(x = "Cumulative hours practiced before the first practice of each piece",
       y = "Hours needed to learn a piece",
       subtitle = "Pieces of a similar difficulty become faster to learn")+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  theme(legend.position = "top")

2.10 Does “pausing” a piece impact the total time required to learn it?

How do we differentiate between pieces that we learn once and those that we come back to repeatedly? Examples could include wanting to improve the playing further, loving it so much we wanted to relearn it, preparing it for a new performance, etc.

As anyone that ever played the piano knows, re-learning a piece, particularly after you “drop” it for a few months/years, results in a much better performance/understanding of the piece. I definitely found that to be true in my experience, particularly with my exam pieces.The downside is that these pieces take longer to learn.

model_data%>%
  mutate(Project_formatted = str_replace_all(Project,"[^[:graph:]]", " "),
         Project_label = as.factor(ifelse(Max_Break > 31, Project_formatted, "")))%>%
  
  ggplot(aes(as.integer(Max_Break), Duration, col = Max_Break <= 31))+
  geom_point(size = 3)+
  geom_text_repel(aes(label = Project_label), size = 3, show.legend = FALSE)+
  scale_x_log10()+
  scale_color_tron(labels = c(TRUE, FALSE))+
  guides(colour = guide_legend(reverse=TRUE))+
  labs(x = "Maximum days passed between two consecutive sessions on the same piece (log scale)",
       y = "Hours needed to learn a piece",
       col = "Break (over 1 month)",
       subtitle = "Taking a break before finishing a piece might lead to more hours required to learn it")+
  theme_ipsum_es()+
  theme(legend.position = "top")

2.11 Repertoire

model_data%>%
  select(-Days_Practiced, -Date_End, -Max_Break)%>%
  mutate(Duration = round(Duration),
         Length = round(Length, 1))%>%
  arrange(desc(Date_Start))%>%
  rename(Experience = Cumulative_Duration,
         Started = Date_Start)%>%
  relocate(Project, Duration, Genre, ABRSM, Level, Standard, Length, Experience, Break, Started)%>%
  
  kbl(escape = FALSE,caption = "Repertoire")%>%
  kable_paper(c("hover", "striped"), full_width = F)%>%
  column_spec(c(1,2), bold = T, color = "black")%>%
  scroll_box(height = "450px")
Repertoire
Project Duration Genre ABRSM Level Standard Length Experience Break Started
Elton John - Rocket man 47 Modern 7 Advanced Performance 4.0 1130 No 2020-12-08
Schumann - Träumerei 14 Romantic 7 Advanced Average 3.0 1087 No 2020-11-09
Mozart - Allegro (3rd movement) K282 28 Classical 6 Intermediate Average 3.3 1081 Yes 2020-11-05
Ibert - Sérénade sur l’eau 10 Modern 6 Intermediate Performance 1.7 1038 No 2020-09-24
Kuhlau - Rondo Vivace 24 Classical 6 Intermediate Average 2.2 1014 No 2020-08-03
C. Hartmann - The little ballerina 21 Romantic 6 Intermediate Performance 2.0 998 No 2020-07-14
Schumann - Lalling Melody 5 Romantic 1 Beginner Average 1.3 981 No 2020-06-28
Schumann - Melody 4 Romantic 1 Beginner Average 1.0 972 No 2020-06-20
Clementi - Sonatina no 3 - Mov 2 3 Classical 1 Beginner Performance 1.0 952 No 2020-06-04
Clementi - Sonatina no 3 - Mov 3 20 Classical 4 Beginner Performance 2.0 952 No 2020-06-04
Chopin - Waltz in Fm 27 Romantic 6 Intermediate Performance 2.0 895 Yes 2020-04-18
Clementi - Sonatina no 3 - Mov 1 30 Classical 4 Beginner Performance 2.7 877 No 2020-04-07
Schumann - Kinderszenen 1 10 Romantic 5 Intermediate Average 2.0 855 No 2020-03-25
Bach - Prelude in G from Cello Suite No 1 25 Baroque 5 Intermediate Average 2.5 788 No 2020-02-04
Georg Böhm - Minuet in G 7 Baroque 1 Beginner Average 1.0 780 Yes 2020-01-27
Bach - Invention 4 in Dm 21 Baroque 5 Intermediate Performance 1.7 777 No 2020-01-25
Chopin - Contredanse in Gb 23 Romantic 6 Intermediate Performance 2.2 762 No 2020-01-16
Bach - Minuet in Gm - 115 7 Baroque 1 Beginner Average 1.3 750 No 2020-01-07
Bach - Minuet in G - 114 4 Baroque 1 Beginner Average 2.0 726 No 2019-12-06
Elton John - Your song (Arr Cornick) 36 Modern 5 Intermediate Performance 3.3 713 No 2019-11-21
Poulenc - Valse Tyrolienne 17 Modern 5 Intermediate Performance 1.7 562 No 2019-09-02
Bach - Prelude in Cm - 934 25 Baroque 5 Intermediate Performance 2.4 536 No 2019-08-15
Schumann - Volksliedchen 10 Romantic 2 Beginner Average 1.8 501 No 2019-07-01
Haydn - Andante in A 39 Classical 5 Intermediate Average 2.8 468 Yes 2019-06-08
Schumann - Remembrance 34 Romantic 5 Intermediate Performance 2.2 422 Yes 2019-04-28
Bach - Minuet in G - 116 8 Baroque 1 Beginner Average 2.0 361 Yes 2019-03-04
Bach - Invention 1 in C 27 Baroque 5 Intermediate Performance 1.7 350 Yes 2019-02-22
Chopin - Waltz in Am 26 Romantic 4 Beginner Performance 2.5 305 Yes 2019-01-07

3 Modelling

Question: How long would it take to learn a piece based on various factors?

3.1 detect outliers

Given the very limited data at the advanced level (Grade 7 ABRSM), those two pieces will be removed. One is an extreme outlier as well which will significantly impact our models.

model_data <- model_data%>%filter(ABRSM != 7)%>%droplevels()

3.2 missing values

There are no missing values in the modelling dataset following the ETL process.

3.3 Feature engineering

  • categorical:
    • ABRSM grade: 1 to 8
    • Genre: Baroque, Classical, Romantic, Modern
    • Break: learning it continuously or setting it aside for a while (1 month minimum)
    • Standard of practice: public performance or average (relative to someone’s level of playing)
  • numerical:
    • Experience: total hours practiced before the first practice session on each piece
    • piece Length: minutes

3.4 Pre-processing

Let’s use some basic standardisation offered by the caret package such as centering (subtract mean from values) and scaling (divide values by standard deviation).

set.seed(123)

# set a backup variable
backup <- model_data

y <- model_data$Duration

# take out our response variable temporarily as we do not want this to be processed
model_data <- model_data %>%
  select(-Duration)

# center and scale our data (BoxCox if needed)
preProcess_range_model <- preProcess(model_data, method=c("center", "scale")) 

model_data <- predict(preProcess_range_model, newdata = model_data)
 
# append the Y variable back on with original values
model_data$Duration <- y

3.5 Resampling

Given the small size of the dataset, bootstrapping resampling method will be applied.

train.control <- trainControl(method = "boot",
                              number = 25,
                              search = "random")

3.6 Model selection

# set number of clusters 
clusters <- 4

# run them all in parallel
cl <- makeCluster(clusters, type = "SOCK")
 
# register cluster train in paralel
registerDoSNOW(cl)

# train models
model <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                  data = model_data,
                  method = "ranger",
                  tuneLength = 100,
                  trControl = train.control)


model2 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "lmStepAIC",
                tuneLength = 100,
                trControl = train.control)


model3 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "lm",
                tuneLength = 100,
                trControl = train.control)

model4 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "ridge",
                tuneLength = 100,
                trControl = train.control)

model5 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "rf",
                tuneLength = 100,
                trControl = train.control)

model6 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "gbm",
                tuneLength = 100,
                trControl = train.control)

model7 <- train(Duration ~ ABRSM + Genre + Length + Cumulative_Duration + Break + Standard,
                data = model_data,
                method = "pls",
                tuneLength = 100,
                trControl = train.control)
 
# shut the instances of R down
stopCluster(cl)

# compare models
model_list <- list(ranger = model, lmStepAIC = model2, lm = model3, ridge = model4, rf = model5, gbm = model6, pls = model7)

model_comparison <- resamples(model_list)

# learning curves to indicate overfitting and underfitting
# hyper parameters 
# https://topepo.github.io/caret/model-training-and-tuning.html#model-training-and-parameter-tuning
# https://topepo.github.io/caret/random-hyperparameter-search.html

We chose the Random Forest model as it was the best performing model. It is known as a model which is:

  • not very sensitive to outliers
  • good for non-linearity
  • variable importance can be biased if categorical variables have few levels (toward high levels) or are correlated
summary(model_comparison)
## 
## Call:
## summary.resamples(object = model_comparison)
## 
## Models: ranger, lmStepAIC, lm, ridge, rf, gbm, pls 
## Number of resamples: 25 
## 
## MAE 
##               Min.  1st Qu.   Median     Mean  3rd Qu.      Max. NA's
## ranger    3.216848 4.546412 5.226721 5.350732 6.185954  8.074644    0
## lmStepAIC 4.059373 6.035128 6.625829 7.373961 8.656703 12.293508    0
## lm        3.457466 6.104998 6.831580 7.407868 8.107952 16.638647    0
## ridge     3.514506 5.195608 5.904643 5.936369 7.002558  7.751690   10
## rf        2.867137 5.123509 5.592698 5.959335 6.989707  8.722035    0
## gbm       4.593238 6.146348 6.951988 7.261349 8.076775 16.990824    0
## pls       4.460543 5.041959 5.930350 5.848360 6.295627  7.970644    0
## 
## RMSE 
##               Min.  1st Qu.   Median     Mean   3rd Qu.      Max. NA's
## ranger    4.531594 5.821475 6.907062 6.876131  7.614550 10.521156    0
## lmStepAIC 4.678084 7.290730 8.706652 9.067898 10.179064 15.976038    0
## lm        3.979872 8.065115 8.705383 9.404728 10.412562 19.451739    0
## ridge     4.989087 6.206758 6.862180 7.045983  7.854493  8.687658   10
## rf        3.394308 6.640020 7.115649 7.546307  8.461888 11.065527    0
## gbm       5.906910 7.466537 8.041088 8.843606  8.994092 21.377506    0
## pls       5.322380 6.303446 7.096138 7.043631  7.345444  9.624458    0
## 
## Rsquared 
##                   Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## ranger    0.2598148174 0.5915672 0.6861549 0.6395133 0.7899287 0.8801139    0
## lmStepAIC 0.0005882529 0.4001020 0.5735153 0.5051570 0.6597403 0.8997367    0
## lm        0.0019980780 0.2879700 0.5871362 0.4965277 0.6363397 0.8958706    0
## ridge     0.3743315404 0.6559275 0.6807293 0.6828744 0.7764227 0.8621330   10
## rf        0.1341300382 0.5248279 0.6068158 0.5858350 0.7012294 0.8842650    0
## gbm       0.0669333944 0.3848695 0.5685371 0.5338006 0.6661362 0.8728232    0
## pls       0.3579501758 0.6342263 0.6727956 0.6723318 0.7638235 0.8898141    0

Based on our regression model, it does not look like we have significant multicollinearity between the full model variables so we can continue as it is.

tidy(vif(model3$finalModel))%>%
  rename(VIF = x)%>%
  mutate(VIF = round(VIF, 1))%>%
  arrange(desc(VIF))%>%
  kbl(caption = "Variance Inflation Factor (VIF)")%>%
  kable_paper("hover", full_width = F)
Variance Inflation Factor (VIF)
names VIF
ABRSM6 4.0
Cumulative_Duration 3.9
ABRSM5 3.5
ABRSM4 3.4
GenreClassical 2.5
Length 2.4
StandardPerformance 2.4
BreakNo 2.1
GenreRomantic 1.9
ABRSM2 1.7
GenreModern 1.7

3.6.1 Actuals vs Predictions

selected_model <- model5

#Saving the model
saveRDS(selected_model, file = "model.rda")

#get predictions
predictions <- predict(selected_model, model_data)

#create dataset
model_data2 <- model_data
model_data2$Predicted <- predictions
model_data2$Actual <- model_data$Duration
model_data2$Residuals <- model_data2$Actual - model_data2$Predicted

# model_data2 <- model_data%>%
#   mutate(Actual = as.numeric(Duration),
#          Predicted = as.numeric(predictions),
#          Residuals = Actual - Predicted)%>%
#   select(Predicted, Actual, Residuals, Project, Level, Genre)

#visualise predicted vs actual
ggplotly(
ggplot(model_data2, aes(Predicted, Actual, label = Residuals, col = Level))+
  geom_point(aes(text = Project), size = 3, alpha = 0.75)+
  geom_smooth(method = "loess", col = "red", lwd = 1, se = FALSE)+
  geom_abline(lty = "dashed", lwd = 0.5, col = "gray")+
  coord_cartesian(xlim = c(0,50), ylim = c(0,50))+
  labs(col = NULL)+
  scale_color_tron()+
  theme_ipsum_es() +
  theme(legend.position = "top")
) %>%
  layout(legend = list(orientation = "h", x = 0.4, y = 1.2))

3.6.2 Residual distribution

We can see that the residuals are mostly situated around 0.

ggplot(model_data2, aes(Residuals, fill = ..count..))+
  geom_histogram(binwidth = 1, col = "black")+
  geom_vline(aes(xintercept=mean(Residuals)), lwd = 1, lty = 2) +
  labs(x="Residuals",
       y= "Total occurences")+
  scale_fill_gradient(low="yellow", high="red")+
  theme_ipsum_es()+
  theme(legend.position = "none")

3.6.3 Actuals versus Residuals

Looking at the variability of errors, there is still a tendency to over-predict for pieces that took very little and under-predict for the more difficult ones. There could be two main reasons for this:

  • practicing an old piece in order to further improve (which naturally adds more practice time as I re-learn it)
  • learning easier pieces later on in my journey which means I will learn them faster than expected (based on my earlier data where a piece of a similar difficulty took longer)
ggplotly(
ggplot(model_data2, aes(Actual, Residuals, col = Level, label = Predicted))+
  geom_hline(yintercept = 0, size = 3, color = "grey52")+
  geom_point(aes(text = Project), alpha = 0.75, size = 3)+
  geom_smooth(method = "loess", col = "red", se = FALSE)+
  labs(col = NULL)+
  scale_color_tron()+
  theme_ipsum_es()
) %>%
  layout(legend = list(orientation = "h",x = 0.4, y = 1.2))

3.7 Linear Regression (LR) or Random Forest (RF)?

We can see that the Random Forest performed significantly better than the Linear Regression model. This isn’t surprising since there might be non-linear trends within the data, and RFs are known to be more accurate.

tidy(compare_models(model3, model5))%>%
  kbl(caption = "Model 1 vs model 2")%>%
  kable_paper("hover", full_width = F)
Model 1 vs model 2
estimate statistic p.value parameter conf.low conf.high method alternative
1.858421 3.01994 0.0059183 24 0.5883319 3.12851 One Sample t-test two.sided

3.8 How many predictors did the most optimal model have?

plot(model5, main = "The most optimal model was that with 6 predictors", col = "orange", lwd = 1.5)

3.9 What were the most important variables?

We can now see that the most important variables seemed to be the length of the piece, my experience prior to starting a piece and time difficulty of the piece. These were also confirmed by the linear regression model.

imp <- as.matrix(varImp(model5)$importance)%>%
  as.data.frame()%>%
  rename(Importance = Overall)%>%
  mutate(Feature = as.factor(rownames(.)),
         Feature = reorder(Feature, Importance))

ggplot(imp, aes(Feature, Importance))+
  geom_segment(aes(Feature, y = 0, xend = Feature, yend = Importance), col = "black", size = 1.5) +
  geom_point(size = 10, col = "orange")+
  geom_text(aes(label = paste(round(Importance), "%", sep = "")), color = "black", size = 3, check_overlap = TRUE)+
  scale_color_tron()+
  scale_fill_tron()+
  theme_ipsum_es()+
  coord_flip()+
  labs(title = "Variable importance ranking")+
  theme(axis.text.x =  element_blank(), 
        axis.ticks = element_blank())

#plot(varImp(model5))

4 Limitations

5 Hardest things about this analysis:

6 Interactive application:

7 What’s next?

LS0tCiAgICBvdXRwdXQ6CiAgICAgIGh0bWxfZG9jdW1lbnQ6CiAKICAgICAgICB0b2M6IHRydWUKICAgICAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICAgICAgdG9jX2RlcHRoOiAyCiAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICAgICAgCiAgICAgICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICAgIAogICAgICAgIGZpZ193aWR0aDogMTUgCiAgICAgICAgZmlnX2hlaWdodDogNgogICAgICAgIGZpZ19hbGlnbjogImNlbnRlciIKICAgICAgICAKICAgICAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICAgICAgdGhlbWU6IGNlcnVsZWFuCiAgICAgICAgCiAgICB0aXRsZTogIkh1bWFuIExlYXJuaW5nIG1lZXRzIE1hY2hpbmUgTGVhcm5pbmciCiAgICBzdWJ0aXRsZTogIjEsMjAwKyBob3VycyBvZiBwaWFubyBwcmFjdGljZSIKICAgIGF1dGhvcjogImJ5IFBldGVyIEhvbnRhcnUiCi0tLQoKIyBJbnRyb2R1Y3Rpb24KCiMjIFdoYXQgYW0gSSBob3BpbmcgdG8gYWNoaWV2ZSB3aXRoIHRoaXMgcHJvamVjdD8KCiogcHJlZGljdCBob3cgbG9uZyBpdCB3aWxsIHRha2UgdG8gbGVhcm4gYSBwaWVjZSBiYXNlZCBvbiB2YXJpb3VzIGZlYXR1cmVzCiogZGlzY292ZXIgaW5zaWdodHMgaW50byBteSBwcmFjdGljZSBoYWJpdHMgYW5kIGZpbmQgYXJlYXMgd2hlcmUgSSBuZWVkIHRvIGltcHJvdmUKKiBkZXZlbG9wIGEgcmVjb21tZW5kZXIgdG9vbCBvZiBwaWFubyBwaWVjZXMgZm9yIG90aGVycyB0byBzZWxlY3QgZnJvbSBiYXNlZCBvbiBmYWN0b3JzIG9mIGludGVyZXN0CiogaG9wZWZ1bGx5IGFjdCBhcyBhIHNvdXJjZSBvZiBpbnNwaXJhdGlvbi9yZWFsaXN0aWMgcGlhbm8gcHJvZ3Jlc3MgZm9yIG90aGVycyB3aG8gd2FudCB0byBsZWFybiBhIG11c2ljYWwgaW5zdHJ1bWVudAoKKipDb250ZXh0Kio6IEkgc3RhcnRlZCBwbGF5aW5nIHRoZSBwaWFubyBpbiAyMDE4IGFzIGEgY29tcGxldGUgYmVnaW5uZXIgYW5kIEkndmUgYmVlbiB0cmFja2luZyBteSBwcmFjdGljZSB0aW1lIGZvciBhcm91bmQgMiBhbmQgYSBoYWxmIHllYXJzLiBJIG5vdyBkZWNpZGVkIHRvIHB1dCB0aGF0IHRvIGdvb2QgdXNlIGFuZCBzZWUgd2hhdCBpbnRlcmVzdGluZyBwYXR0ZXJucyBJIG1pZ2h0IGJlIGFibGUgdG8gZmluZC4KCiMjIERhdGEgY29sbGVjdGlvbgoKKiBpbXB1dGVkIGNvbnNlcnZhdGl2ZSBlc3RpbWF0aW9ucyBmb3IgdGhlIGZpcnN0IDEwIG1vbnRocyBvZiB0aGUgZmlyc3QgeWVhciAoSmFuICcxOCB0byBPY3QgJzE4KQoqIGV2ZXJ5dGhpbmcgZnJvbSBEZWMgJzE4IG9ud2FyZHMgd2FzIHRyYWNrZWQgdXNpbmcgVG9nZ2wsIGEgdGltZS10cmFja2luZyBhcHAvdG9vbAoqIHRoZXNlIHRpbWVzIGluY2x1ZGUgbXkgcHJhY3RpY2Ugc2Vzc2lvbnMgKHVzdWFsbHkgaW4gc2hvcnQgYm91dHMgb2YgMzAgbWludXRlcyk7IHBpYW5vIGxlc3NvbnMgYXJlIGV4Y2x1ZGVkICh1c3VhbGx5IDItMyBob3VycyB0b3RhbCBwZXIgbW9udGgpCiogdGhlICoqRXh0cmFjdCwgVHJhbnNmb3JtLCBMb2FkKiogc2NyaXB0IGlzIGF2YWlsYWJsZSBpbiB0aGUgZ2xvYmFsLlIgZmlsZSBvZiB0aGlzIHJlcG8KCioqRGlzY2xhaW1lcioqOiBJIGFtIG5vdCBhZmZpbGlhdGVkIHdpdGggVG9nZ2wuIEkgc3RhcnRlZCB1c2luZyBpdCBhIGZldyB5ZWFycyBhZ28gYmVjYXVzZSBpdCBwcm92aWRlZCBhbGwgdGhlIGZ1bmN0aW9uYWxpdHkgSSBuZWVkZWQgYW5kIGxvdmVkIGl0cyBtaW5pbWFsaXN0aWMgZGVzaWduLiBUaGUgc3RhbmRhcmQgbWVtYmVyc2hpcCwgd2hpY2ggSSB1c2UsIGlzIGZyZWUgb2YgY2hhcmdlLgoKS2V5IHBvaW50czoKCiogaWRlbnRpZmllZCB2YXJpb3VzIHRyZW5kcyBiYXNlZCBvbiBteSBwcmFjdGljZQoqIHByZWRpY3RlZCBhbiBhbGdvcml0aG0KKiBwcmFjdGljZWQgbGVzcyBpbiBsb2NrZG93bgoqIGltcGFjdCBvZiBzb21ldGhpbmcgbGlrZSBzaWdodCByZWFkaW5nIG9uIGxlYXJuaW5nIGEgcGllY2UgZmFzdGVyIC8gdGVjaG5pcXVlIHdvcmsgKHdoaWNoIEkgbm93IHRyYWNrKQoqIHRlc3Rpbmcgb24gY3VycmVudCBwaWVjZXMKCmBgYHtyfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgICBlY2hvID0gVFJVRSwgIyBzaG93IGFsbCBvZiB0aGUgY29kZQogICAgdGlkeSA9IEZBTFNFLCAjIGNsZWFuZXIgY29kZSBwcmludGluZwogICAgc2l6ZSA9ICJzbWFsbCIsICMgc21hbGxlciBjb2RlCiAgICAKICAgIGZpZy5wYXRoID0gImZpZ3MvIiwjIHdoZXJlIHRoZSBmaWd1cmVzIHdpbGwgZW5kIHVwCiAgICBvdXQud2lkdGggPSAiMTAwJSIsCgogICAgbWVzc2FnZSA9IEZBTFNFLAogICAgd2FybmluZyA9IEZBTFNFCiAgICApCmBgYAoKYGBge3IgaW5jbHVkZT1GQUxTRX0KIyBydW4gdGhlIGltcG9ydC9jbGVhbiBzY3JpcHQKc291cmNlKCJnbG9iYWwuUiIpCmBgYAoKIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpCgojIyBQaWFubyBwcmFjdGljZSB0aW1lbGluZQoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTV9CnJhd19kYXRhJT4lCiAgZ3JvdXBfYnkoTW9udGhfZm9ybWF0KSU+JQogIHN1bW1hcmlzZShUb3RhbF9EdXJhdGlvbiA9IHN1bShEdXJhdGlvbikvNjApJT4lCiAgbXV0YXRlKFRvdGFsX0R1cmF0aW9uMiA9IGFzLmludGVnZXIoY3Vtc3VtKFRvdGFsX0R1cmF0aW9uKSksCiAgICAgICAgIG1heCA9IGFzLmludGVnZXIobWF4KFRvdGFsX0R1cmF0aW9uMikpLAogICAgICAgICBtYXggPSBpZmVsc2UobWF4ID4gVG90YWxfRHVyYXRpb24yLCAiIiwgbWF4KSklPiUKCiAgZ2dwbG90KGFlcyhNb250aF9mb3JtYXQsIFRvdGFsX0R1cmF0aW9uMiwgZ3JvdXAgPSAxKSkrCiAgZ2VvbV9saW5lKHNpemUgPSAyLCBjb2xvciA9ICIjNjliM2EyIikrCiAgZ2VvbV9wb2ludChzaXplID0gNSwgY29sb3IgPSAiIzY5YjNhMiIpKwogIGdlb21fYXJlYShhbHBoYSA9IDAuMywgZmlsbCA9ICIjNjliM2EyIikrCiAgIyBncmFkZSAzCiAgZ2VvbV9wb2ludCh4PSJPY3RcbiAnMTgiLCB5ID0gMjUzLCBzaXplID0gNSwgY29sb3IgPSAiZGFyayByZWQiKSsKICBnZW9tX3RleHQoeD0iT2N0XG4gJzE4IiwgeSA9IDI1MysyMDAsIHNpemUgPSA1LCBsYWJlbCA9ICJHcmFkZSAzIikrCiAgZ2VvbV90ZXh0KHg9Ik9jdFxuICcxOCIsIHkgPSAyNTMrMTAwLCAgc2l6ZSA9IDUsIGxhYmVsID0gIjI1MyBob3VycyIpKwogICMgZ3JhZGUgNQogIGdlb21fcG9pbnQoeD0iT2N0XG4gJzE5IiwgeSA9IDY3NSwgc2l6ZSA9IDUsIGNvbG9yID0gImRhcmsgcmVkIikrCiAgZ2VvbV90ZXh0KHg9Ik9jdFxuICcxOSIsIHkgPSA2NzUrMjAwLCAgc2l6ZSA9IDUsIGxhYmVsID0gIkdyYWRlIDUiKSsKICBnZW9tX3RleHQoeD0iT2N0XG4gJzE5IiwgeSA9IDY3NSsxMDAsICBzaXplID0gNSwgbGFiZWwgPSAiNjc1IGhvdXJzIikrCiAgIyBncmFkZSA2CiAgZ2VvbV9wb2ludCh4PSJPY3RcbiAnMjAiLCB5ID0gMTA3OCwgc2l6ZSA9IDUsIGNvbG9yID0gImRhcmsgcmVkIikrCiAgZ2VvbV90ZXh0KHg9Ik9jdFxuICcyMCIsIHkgPSAxMDc4KzIwMCwgIHNpemUgPSA1LCBsYWJlbCA9ICJHcmFkZSA2IikrCiAgZ2VvbV90ZXh0KHg9Ik9jdFxuICcyMCIsIHkgPSAxMDc4KzEwMCwgIHNpemUgPSA1LCBsYWJlbCA9ICIxMDc4IGhvdXJzIikrCiAgIyBOT1cKICBnZW9tX3BvaW50KGFlcyh4PSJBcHJcbiAnMjEiLCB5ID0gMTIxOSksIHNpemUgPSA1LCBjb2xvciA9ICJkYXJrIHJlZCIpKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBtYXgpLCBudWRnZV95ID0gNzUsIG51ZGdlX3ggPSAtMC41LCBzaXplID0gNSkrCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9InllbGxvdyIsIGhpZ2g9InJlZCIpKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRvdGFsIGhvdXJzIG9mIHByYWN0aWNlIiwKICAgICAgIHRpdGxlID0gIlBpYW5vIHByYWN0aWNlIHRpbWVsaW5lIikrCiAgdGhlbWVfaXBzdW1fZXMoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgojIyBIb3cgbG9uZyBkaWQgSSBwcmFjdGljZSBwZXIgcGllY2U/CgpCYXNlZCBvbiB0aGUgbGV2ZWwgYXQgdGhlIHRpbWUgYW5kIHRoZSBkaWZmaWN1bHR5IG9mIHRoZSBwaWVjZSwgd2UgY2FuIHNlZSB0aGF0IGVhY2ggcGllY2UgdG9vayBhcm91bmQgMTAtMzAgaG91cnMgb2YgcHJhY3RpY2UuCgpgYGB7ciB0aW1lbGluZX0KcmF3X2RhdGElPiUKICBmaWx0ZXIoRGF0ZV9TdGFydCA+IGFzLkRhdGUoIjIwMTgvMTEvMDEiKSklPiUKICBmaWx0ZXIoQ29tcGxldGVkID09ICJZZXMiKSU+JQogIGdyb3VwX2J5KFByb2plY3QsIERhdGVfU3RhcnQpJT4lCiAgc3VtbWFyaXNlKER1cmF0aW9uID0gc3VtKER1cmF0aW9uKS82MCklPiUKICBtdXRhdGUoQ3VtdWxhdGl2ZV9QaWVjZSA9IGN1bXN1bShEdXJhdGlvbiksCiAgICAgICAgIE1vbnRoX1llYXIgPSBhcy5mYWN0b3IoYXMueWVhcm1vbihEYXRlX1N0YXJ0KSksCiAgICAgICAgIE1vbnRoX2Zvcm1hdCA9IHN0cl9yZXBsYWNlKE1vbnRoX1llYXIsICIgMjAiLCAiXG4gJyIpKSU+JQogIHVuZ3JvdXAoKSU+JQogIG11dGF0ZShDdW11bGF0aXZlX1RvdGFsID0gY3Vtc3VtKER1cmF0aW9uKSklPiUKICBmaWx0ZXIoUHJvamVjdCAlbm90aW4lIGMoIlRlY2huaXF1ZSIsICJHZW5lcmFsIiwgIlNpZ2h0cmVhZGluZyIpKSU+JQogIGxlZnRfam9pbihtb2RlbF9kYXRhJT4lc2VsZWN0KExldmVsLCBQcm9qZWN0LCBBQlJTTSksIGJ5ID0gIlByb2plY3QiKSU+JQogIApnZ3Bsb3QoYWVzKERhdGVfU3RhcnQsIEN1bXVsYXRpdmVfUGllY2UsIGZpbGwgPSBMZXZlbCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAxMCwgc2hhcGUgPSAyMSwgY29sID0gImJsYWNrIiwgYWxwaGEgPSAwLjUpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYyguMSwgMTYpLCBndWlkZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICdZZWFyOiB7ZnJhbWVfdGltZX0nLAogICAgICAgeSA9ICJUb3RhbCBwcmFjdGljZSB0aW1lIHBlciBwaWVjZSAoaG91cnMpIikrCiAgc2NhbGVfY29sb3JfdHJvbigpKwogIHNjYWxlX2ZpbGxfdHJvbigpKwogIHRoZW1lX2lwc3VtX2VzKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSsKICB0cmFuc2l0aW9uX3RpbWUoRGF0ZV9TdGFydCkgKwogIGVhc2VfYWVzKCdsaW5lYXInKSsKICBleGl0X2ZhZGUoKSArCiAgc2hhZG93X21hcmsoYWxwaGEgPSAwLjEsIHNpemUgPSA1KQoKIyBzYXZlIGFuaW1hdGlvbiBhcyBnaWYgZm9yIGxhdGVyIHVzZQojIGFuaW1fc2F2ZSgiZmlncy90aW1lbGluZS5naWYiKQpgYGAKCiMjIEhvdyBjb25zaXN0ZW50IHdhcyBteSBwcmFjdGljZT8KCkdlbmVyYWxseSwgSSd2ZSBkb25lIHByZXR0eSB3ZWxsIHRvIG1haW50YWluIGEgaGlnaCBsZXZlbCBvZiBjb25zaXN0ZW5jeSB3aXRoIHRoZSBleGNlcHRpb24gb2YgQXVndXN0L0RlY2VtYmVyLiBUaGlzIGlzIHVzdWFsbHkgd2hlcmUgSSB0ZW5kIHRvIHRha2UgYW5udWFsIGxlYXZlLgoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTV9CnJhd19kYXRhJT4lCiAgZmlsdGVyKFNvdXJjZSAhPSAiRXN0aW1hdGVkIiklPiUKICBncm91cF9ieShNb250aF9ZZWFyLCBNb250aF9TdGFydCwgTW9udGhfZm9ybWF0KSU+JQogIHN1bW1hcmlzZShEYXlzX1ByYWN0aWNlID0gbl9kaXN0aW5jdChEYXRlX1N0YXJ0KSwKICAgICAgICAgICAgVG90YWxfRHVyYXRpb24gPSBzdW0oRHVyYXRpb24sIG5hLnJtID0gVFJVRSkpJT4lCiAgbXV0YXRlKERheXNfVG90YWwgPSBkYXlzX2luX21vbnRoKE1vbnRoX1N0YXJ0KSwKICAgICAgICAgRGF5c19Ob3RfUHJhY3RpY2VkID0gRGF5c19Ub3RhbCAtIERheXNfUHJhY3RpY2UsCiAgICAgICAgIEF2Z19EdXJhdGlvbiA9IGFzLmludGVnZXIoVG90YWxfRHVyYXRpb24vRGF5c19Ub3RhbCksCiAgICAgICAgIENvbnNpc3RlbmN5ID0gcm91bmQoRGF5c19QcmFjdGljZSAvIERheXNfVG90YWwgKiAxMDAsMiksCiAgICAgICAgIENvbnNpc3RlbmN5X1N0YXR1cyA9IGlmZWxzZShDb25zaXN0ZW5jeTw3NSwgIkJhZCIsICJHb29kIiksCiAgICAgICAgIE1vbnRoX2Zvcm1hdCA9IHJlb3JkZXIoTW9udGhfZm9ybWF0LCBNb250aF9ZZWFyKSklPiUKICAKICBnZ3Bsb3QoYWVzKE1vbnRoX2Zvcm1hdCwgQ29uc2lzdGVuY3ksIGZpbGwgPSBDb25zaXN0ZW5jeV9TdGF0dXMpKSsKICBnZW9tX2NvbChncm91cCA9IDEsIGNvbCA9ICJibGFjayIpKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDc1LCBsdHkgPSAiZGFzaGVkIikrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IERheXNfTm90X1ByYWN0aWNlZCksIHNpemUgPSA1LCBudWRnZV95ID0gMykrCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIGZpbGwgPSAiQ29uc2lzdGVuY3kgc3RhdHVzIiwKICAgICAgIHN1YnRpdGxlID0gIk51bWJlcnMgaW5kaWNhdGUgZGF5cyB3aXRob3V0IGFueSBwcmFjdGljZSB3aXRoaW4gZWFjaCBtb250aCIpKwogIHNjYWxlX2NvbG9yX3Ryb24oKSsKICBzY2FsZV9maWxsX3Ryb24oKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCiMjIFdhcyB0aGVyZSBhIHRyZW5kIGluIG15IGFtb3VudCBvZiBkYWlseSBhdmVyYWdlIHByYWN0aWNlPyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KCldlIGNhbiBzZWUgdGhhdCBteSBwcmFjdGljZSB0aW1lIHdhcyBjb3JyZWxhdGVkIHdpdGggdGhlIGNvbnNpc3RlbmN5LCB3aGVyZSB0aGUgYXZlcmFnZSBzZXNzaW9uIHdhcyBtdWNoIHNob3J0ZXIgaW4gdGhlIG1vbnRocyBJIHdhcyBhd2F5IGZyb20gdGhlIHBpYW5vLiBUaGVyZSdzIGFsc28gYSB0cmVuZCB3aGVyZSBteSBwcmFjdGljZSBjbG9zZSB0byBhbiBleGFtIHNlc3Npb24gd2FzIHNpZ25pZmljYW50bHkgaGlnaGVyIHRoYW4gYW55IG90aGVyIHRpbWUgb2YgdGhlIHllYXIuICoqQ2FuIHlvdSBzcG90IGluIHdoaWNoIG1vbnRoIEkgaGFkIG15IGV4YW0gaW4gMjAxOT8gV2hhdCBhYm91dCB0aGUgZW5kIG9mIDIwMjA/KioKCiphdmVyYWdlIHByYWN0aWNlIGxlbmd0aCBwZXIgbW9udGggaW5jbHVkZXMgdGhlIGRheXMgaW4gd2hpY2ggSSBkaWQgbm90IHByYWN0aWNlKgoKIyMjIG92ZXJhbGwgey19CgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xNX0KcmF3X2RhdGElPiUKICBmaWx0ZXIoU291cmNlICE9ICJFc3RpbWF0ZWQiKSU+JQogIGdyb3VwX2J5KE1vbnRoX1llYXIsIE1vbnRoX1N0YXJ0LCBNb250aF9mb3JtYXQpJT4lCiAgc3VtbWFyaXNlKERheXNfUHJhY3RpY2UgPSBuX2Rpc3RpbmN0KERhdGVfU3RhcnQpLAogICAgICAgICAgICBUb3RhbF9EdXJhdGlvbiA9IHN1bShEdXJhdGlvbikpJT4lCiAgbXV0YXRlKERheXNfVG90YWwgPSBkYXlzX2luX21vbnRoKE1vbnRoX1N0YXJ0KSwKICAgICAgICAgQXZnX0R1cmF0aW9uID0gYXMuaW50ZWdlcihUb3RhbF9EdXJhdGlvbi9EYXlzX1RvdGFsKSwKICAgICAgICAgQXZnX0R1cmF0aW9uX1N0YXR1cyA9IGlmZWxzZShBdmdfRHVyYXRpb24gPCA2MCwgIkxlc3MgdGhhbiBvbmUgaG91ciIsICJPbmUgaG91ciIpLAogICAgICAgICBNb250aF9mb3JtYXQgPSByZW9yZGVyKE1vbnRoX2Zvcm1hdCwgTW9udGhfWWVhcikpJT4lCiAgCiAgZ2dwbG90KGFlcyhNb250aF9mb3JtYXQsIEF2Z19EdXJhdGlvbiwgZmlsbCA9IEF2Z19EdXJhdGlvbl9TdGF0dXMpKSsKICBnZW9tX2NvbChjb2wgPSAiYmxhY2siKSsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBdmVyYWdlIHByYWN0aWNlIHNlc3Npb24gbGVuZ3RoIChtaW4pIiwKICAgICAgIGZpbGwgPSAiU3RhdHVzIikrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IEF2Z19EdXJhdGlvbiksIG51ZGdlX3kgPSA1LCBzaXplID0gNSkrCiAgc2NhbGVfY29sb3JfdHJvbigpKwogIHNjYWxlX2ZpbGxfdHJvbigpKwogIHRoZW1lX2lwc3VtX2VzKCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyBZZWFyIG9uIFllYXIgey19CgpTaW1pbGFyIHRyZW5kcyBhcyBiZWZvcmUgYXJlIGFwcGFyZW50IHdoZXJlIG15IGF2ZXJhZ2UgZGFpbHkgc2Vzc2lvbiBpcyBsb25nZXIgYmVmb3JlIHRoZSBleGFtcyB0aGFuIGFueSBvdGhlciB0aW1lIGluIHRoZSB5ZWFyIGFuZCBhIGRpcCBpbiB0aGUgbW9udGhzIHdoZXJlIEkgdXN1YWxseSB0YWtlIG1vc3Qgb2YgbXkgYW5udWFsIGxlYXZlLiBJIHJlYWxseSBkbyBuZWVkIHRvIHN0YXJ0IHBpY2tpbmcgdXAgdGhlIHBhY2UgYW5kIGdldCBiYWNrIHRvIHdoZXJlIEkgdXNlZCB0byBiZS4KCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTE1fQpyYXdfZGF0YSU+JQogIGdyb3VwX2J5KE1vbnRoX1llYXIsIE1vbnRoX1N0YXJ0LCBNb250aF9mb3JtYXQsIE1vbnRoX05hbWUsIFllYXIpJT4lCiAgc3VtbWFyaXNlKERheXNfUHJhY3RpY2UgPSBuX2Rpc3RpbmN0KERhdGVfU3RhcnQpLAogICAgICAgICAgICBUb3RhbF9EdXJhdGlvbiA9IHN1bShEdXJhdGlvbikpJT4lCiAgbXV0YXRlKERheXNfVG90YWwgPSBkYXlzX2luX21vbnRoKE1vbnRoX1N0YXJ0KSwKICAgICAgICAgQXZnX0R1cmF0aW9uID0gYXMuaW50ZWdlcihUb3RhbF9EdXJhdGlvbi9EYXlzX1RvdGFsKSwKICAgICAgICAgQXZnX0R1cmF0aW9uX1N0YXR1cyA9IGlmZWxzZShBdmdfRHVyYXRpb24gPCA2MCwgIkxlc3MgdGhhbiBvbmUgaG91ciIsICJPbmUgaG91ciIpLAogICAgICAgICBNb250aF9mb3JtYXQgPSByZW9yZGVyKE1vbnRoX2Zvcm1hdCwgTW9udGhfWWVhciksCiAgICAgICAgIHNpemUgPSBhcy5mYWN0b3IoaWZlbHNlKFllYXIgPT0gMjAxOCwgMSwgMS41KSksCiAgICAgICAgIGxhYmVsID0gaWZlbHNlKG1vbnRoKE1vbnRoX1N0YXJ0KSA9PSAxLCBhcy5jaGFyYWN0ZXIoWWVhciksICIiKSklPiUKICAKICBnZ3Bsb3QoYWVzKE1vbnRoX05hbWUsIEF2Z19EdXJhdGlvbiwgZ3JvdXAgPSBZZWFyLCBzaXplID0gc2l6ZSkpKwogIGdlb21fbGluZShhZXMoY29sID0gWWVhcikpKwogIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gbGFiZWwsIGNvbCA9IFllYXIpKSsKICBsYWJzKHggPSBOVUxMLAogICAgICAgZmlsbCA9ICJTdGF0dXMiKSsKICBzY2FsZV9jb2xvcl90cm9uKCkrCiAgc2NhbGVfZmlsbF90cm9uKCkrCiAgdGhlbWVfaXBzdW1fZXMoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKIyMgRGlkIENPVklEIHNpZ25pZmljYW50bHkgaW1wYWN0IG15IHByYWN0aWNlIHRpbWU/IHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQoKIyMjIGdyYXBoIHstfQoKRGVzcGl0ZSBhIHNpbWlsYXIgbWVkaWFuLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHByYWN0aWNlIHNlc3Npb25zIHdlcmUgbGVzcyBsaWtlbHkgdG8gYmUgb3ZlciA4MCBtaW4gYWZ0ZXIgQ09WSUQuIFdlIGNhbiB0ZXN0IGlmIHRoaXMgd2FzIGEgc2lnbmlmaWNhbnQgaW1wYWN0IHdpdGggYSB0LXRlc3QuCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD05fQpjb3ZpZF9zdGFydCA8LSBhcy5EYXRlKCIyMDIwLzAzLzIzIikKCmluZmVyZW5jZSA8LSByYXdfZGF0YSU+JQogIGZpbHRlcihTb3VyY2UgIT0gIkVzdGltYXRlZCIpJT4lCiAgbXV0YXRlKENvdmlkX1N0YXR1cyA9IGFzLmZhY3RvcihpZmVsc2UoRGF0ZV9TdGFydCA8IGNvdmlkX3N0YXJ0LCAiQmVmb3JlIENPVklEIiwgIkFmdGVyIENPVklEIikpLAogICAgICAgICBDb3ZpZF9TdGF0dXMgPSByZW9yZGVyKENvdmlkX1N0YXR1cywgZGVzYyhDb3ZpZF9TdGF0dXMpKSklPiUKICBncm91cF9ieShDb3ZpZF9TdGF0dXMsIERhdGVfU3RhcnQpJT4lCiAgc3VtbWFyaXNlKER1cmF0aW9uID0gc3VtKER1cmF0aW9uKSklPiUKICB1bmdyb3VwKCkKICAKICBnZ3Bsb3QoaW5mZXJlbmNlLCBhZXMoQ292aWRfU3RhdHVzLCBEdXJhdGlvbiwgZmlsbCA9IENvdmlkX1N0YXR1cykpKwogIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IFRSVUUsIGNvbCA9ICJibGFjayIpKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkF2ZXJhZ2UgcHJhY3RpY2Ugc2Vzc2lvbiAobWluKSIpKwogIHNjYWxlX2NvbG9yX3Ryb24oKSsKICBzY2FsZV9maWxsX3Ryb24oKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgc2tld25lc3MgYXNzdW1wdGlvbiB7LX0KCkdpdmVuIHRoZSBleHRyZW1lbHkgbG93IHAtdmFsdWUsIHRoZSBTaGFwaXJvLVdpbGsgbm9ybWFsaXR5IHRlc3QgaW1wbGllcyB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRhdGEgaXMgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb24gYW5kIHRoYXQgd2UgY2Fubm90IGFzc3VtZSB0aGUgbm9ybWFsaXR5LiBIb3dldmVyLCB3ZSdyZSB3b3JraW5nIHdpdGggdGhlIGVudGlyZSBwb3B1bGF0aW9uIGRhdGFzZXQgZm9yIGVhY2ggY2xhc3MgYW5kIHRodXMsIHVubGlrZSB0aGUgaW5kZXBlbmRlbmNlIG9mIGRhdGEsIHRoaXMgYXNzdW1wdGlvbiBpcyBub3QgY3J1Y2lhbC4KICAKYGBge3J9CiAgaW5mZXJlbmNlICU+JSAKICBzZWxlY3QoQ292aWRfU3RhdHVzLCBEdXJhdGlvbikgJT4lIAogIGdyb3VwX2J5KGdyb3VwID0gYXMuY2hhcmFjdGVyKENvdmlkX1N0YXR1cykpICU+JQogIGRvKHRpZHkoc2hhcGlyby50ZXN0KC4kRHVyYXRpb24pKSklPiUKICBrYmwoY2FwdGlvbiA9ICJTaGFwaXJvLVdpbGsgbm9ybWFsaXR5IHRlc3QiKSU+JQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQoKYGBgCgojIyMgZXF1YWwgdmFyaWFuY2VzIGFzc3VtcHRpb24gey19CgpXZSBjYW4gc2VlIHRoYXQgd2l0aCBhIGxhcmdlIHAgdmFsdWUsIHdlIHNob3VsZCBmYWlsIHRvIHJlamVjdCB0aGUgTnVsbCBoeXBvdGhlc2lzIChIbykgYW5kIGNvbmNsdWRlIHRoYXQgd2UgZG8gbm90IGhhdmUgZXZpZGVuY2UgdG8gYmVsaWV2ZSB0aGF0IHBvcHVsYXRpb24gdmFyaWFuY2VzIGFyZSBub3QgZXF1YWwgYW5kIHVzZSB0aGUgZXF1YWwgdmFyaWFuY2VzIGFzc3VtcHRpb24gZm9yIG91ciB0IHRlc3QKCmBgYHtyfQp0aWR5KGxldmVuZVRlc3QoaW5mZXJlbmNlJER1cmF0aW9ufmluZmVyZW5jZSRDb3ZpZF9TdGF0dXMpKSU+JQogIGtibChjYXB0aW9uID0gIkxldmVuZSdzIHRlc3QiKSU+JQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQpgYGAKCiMjIyB0LXRlc3Qgey19CgpNeSBwcmFjdGljZSBzZXNzaW9ucyBwb3N0LUNPVklEIGFyZSBzaWduaWZpY2FudGx5IHNob3J0ZXIgdGhhbiB0aG9zZSBiZWZvcmUgdGhlIHBhbmRlbWljLiBUaGlzIG1pZ2h0IGJlIHN1cnByaXNpbmcsIGdpdmVuIHRoYXQgd2Ugd2VyZSBpbiB0aGUgbG9ja2Rvd24gbW9zdCBvZiB0aGUgdGltZS4gSG93ZXZlciwgSSd2ZSBiZWVuIHNwZW5kaW5nIG15IHRpbWUgZG9pbmcgYSBmZXcgb3RoZXIgdGhpbmdzIHN1Y2ggYXMgaW1wcm92aW5nIG15IHRlY2huaWNhbCBza2lsbHNldCB3aXRoIFIgKHRoaXMgYW5hbHlzaXMgd291bGRuJ3QgaGF2ZSBiZWVuIHBvc3NpYmxlIG90aGVyd2lzZSkgYW5kIGxlYXJuaW5nIGl0YWxpYW4uCgpgYGB7cn0KdF90ZXN0IDwtIGluZmVyZW5jZSU+JQogIHRfdGVzdChEdXJhdGlvbiB+IENvdmlkX1N0YXR1cywgdmFyLmVxdWFsID0gVFJVRSklPiUKICBhZGRfc2lnbmlmaWNhbmNlKCklPiUKICBrYmwoKSU+JQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQoKdF90ZXN0CmBgYAoKIyMgV2hhdCB0eXBlIG9mIG11c2ljIGRvIEkgdGVuZCB0byBwbGF5PyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KCiMjIyBieSBnZW5yZSB7LX0KCmBgYHtyfQpncmFwaF9wcmFjdGljZSA8LSBmdW5jdGlvbih2YXJpYWJsZSwgbnVkZ2UpewogIHJhd19kYXRhJT4lCiAgZmlsdGVyKEdlbnJlICVub3RpbiUgYygiT3RoZXIiLCAiTm90IGFwcGxpY2FibGUiKSklPiUKICBncm91cF9ieSh7e3ZhcmlhYmxlfX0pJT4lCiAgc3VtbWFyaXNlKER1cmF0aW9uID0gYXMuaW50ZWdlcihzdW0oRHVyYXRpb24pLzYwKSklPiUKICBhcnJhbmdlKGRlc2MoRHVyYXRpb24pKSU+JQogIGhlYWQoMTApJT4lCgogIGdncGxvdChhZXMocmVvcmRlcih7e3ZhcmlhYmxlfX0sIER1cmF0aW9uKSwgRHVyYXRpb24sIGZpbGwgPSBEdXJhdGlvbikpKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UsIGNvbCA9ICJibGFjayIsIHdpZHRoID0gMSkrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IER1cmF0aW9uKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgbnVkZ2VfeSA9IG51ZGdlLCBzaXplID0gNSkrCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9InllbGxvdyIsIGhpZ2g9InJlZCIpKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRvdGFsIGhvdXJzIG9mIHByYWN0aWNlIikrCiAgY29vcmRfZmxpcCgpKwogIHRoZW1lX2lwc3VtX2VzKCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKZ3JhcGhfcHJhY3RpY2UoR2VucmUsIDE1KQpgYGAKCiMjIyBieSBjb21wb3NlciB7LX0KCmBgYHtyfQpncmFwaF9wcmFjdGljZShDb21wb3NlciwgNSkKYGBgCgojIyMgYnkgcGllY2Ugey19CgpgYGB7cn0KZ3JhcGhfcHJhY3RpY2UoUHJvamVjdCwgMykKYGBgCgojIyBSZWxhdGlvbiBiZXR3ZWVuIGRpZmZpY3VsdHkgYW5kIG51bWJlciBvZiBwcmFjdGljZSBob3VycyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KCiMjIyBBQlJTTSBncmFkZSB7LX0KClNpbXBsaWZpZWQsIEFCQlJTTSBncmFkZXMgYXJlIGEgZ3JvdXAgb2YgOCBleGFtcyBiYXNlZCBvbiB0aGVpciBkaWZmaWN1bHR5ICgxIC0gYmVnaW5uZXIgdG8gOCAtIGFkdmFuY2VkKS4gVGhlcmUgYXJlIGFsc28gZGlwbG9tYSBncmFkZXMgYnV0IHRob3NlIGFyZSBleHRyZW1lbHkgYWR2YW5jZWQsIGVxdWl2YWxlbnQgdG8gdW5pdmVyc2l0eSBsZXZlbCBzdHVkaWVzIGFuZCBvdXQgb2YgdGhlIHNjb3BlIG9mIHRoaXMgYW5hbHlzaXMuIAoKTW9yZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgb24gdGhlaXIgb2ZmaWNpYWwgd2Vic2l0ZSBhdCBodHRwczovL2diLmFicnNtLm9yZy9lbi9leGFtLXN1cHBvcnQveW91ci1ndWlkZS10by1hYnJzbS1leGFtcy8KCmBgYHtyfQptb2RlbF9kYXRhJT4lCiAgbXV0YXRlKER1cmF0aW9uID0gRHVyYXRpb24pJT4lCiAgCiAgZ2dwbG90KGFlcyhBQlJTTSwgRHVyYXRpb24sIGZpbGwgPSBBQlJTTSkpKwogIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IFRSVUUsIG91dGxpZXIuY29sb3VyID0gInJlZCIpKwogIGxhYnMoeCA9ICJBQlJTTSBHcmFkZSIsCiAgICAgICB5ID0gIlRvdGFsIHByYWN0aWNlIGhvdXJzIiwKICAgICAgIHN1YnRpdGxlID0gIlRoZSBoaWdoZXIgdGhlIGRpZmZpY3VsdHksIHRoZSBtb3JlIHRpbWUgaXQgdGFrZXMgdG8gbGVhcm4gYSBwaWVjZSIpKwogIHNjYWxlX2NvbG9yX3Ryb24oKSsKICBzY2FsZV9maWxsX3Ryb24oKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgbGV2ZWwgey19CgpBIGZ1cnRoZXIgYWdncmVnYXRpb24gb2YgQUJSU00gZ3JhZGVzOyB0aGlzIGlzIGhlbHBmdWwgZ2l2ZW4gdGhlIHZlcnkgbGltaXRlZCBkYXRhc2V0IHdpdGhpbiBlYWNoIGdyYWRlIGFuZCBtdWNoIGVhc2llciBvbiB0aGUgZXllLiBUaGlzIGlzIGFuIG92ZXJzaW1wbGlmaWNhdGlvbiBidXQgdGhleSdyZSBjbGFzc2lmaWVkIGFzOgogICogMS01OiBCZWdpbm5lciAoMSkKICAqIDUtNjogSW50ZXJtZWRpYXRlICgyKQogICogNy04OiBBZHZhbmNlZCAoMykKCmBgYHtyfQptb2RlbF9kYXRhJT4lCiAgbXV0YXRlKER1cmF0aW9uID0gRHVyYXRpb24pJT4lCiAgCiAgZ2dwbG90KGFlcyhMZXZlbCwgRHVyYXRpb24sIGZpbGwgPSBMZXZlbCkpKwogIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IFRSVUUsIG91dGxpZXIuY29sb3VyID0gInJlZCIpKwogIHNjYWxlX2NvbG9yX3Ryb24oKSsKICBzY2FsZV9maWxsX3Ryb24oKSsKICBsYWJzKHggPSAiTGV2ZWwiLAogICAgICAgeSA9ICJUb3RhbCBwcmFjdGljZSBob3VycyIsCiAgICAgICBzdWJ0aXRsZSA9ICJUaGUgaGlnaGVyIHRoZSBkaWZmaWN1bHR5LCB0aGUgbW9yZSB0aW1lIGl0IHRha2VzIHRvIGxlYXJuIGEgcGllY2UiKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyBXaGF0IGFib3V0IHRoZSBwaWVjZSBsZW5ndGg/CgpgYGB7cn0KbW9kZWxfZGF0YSU+JQogIAogIGdncGxvdChhZXMoTGVuZ3RoLCBEdXJhdGlvbiwgZ3JvdXAgPSAxKSkrCiAgZ2VvbV9qaXR0ZXIoYWVzKGNvbCA9IExldmVsKSwgd2lkdGggPSAwLjUsIGhlaWdodCA9IDAuNSwgc2l6ZSA9IDMpKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPUZBTFNFKSsKICBsYWJzKHggPSAiUGllY2UgbGVuZ3RoIChtaW5zKSIsCiAgICAgICB5ID0gIkhvdXJzIG5lZWRlZCB0byBsZWFybiBhIHBpZWNlIiwKICAgICAgIHN1YnRpdGxlID0gIlRoZXJlIGFwcGVhcnMgdG8gYmUgYSBsaW5lYXIgdHJlbmQgYmV0d2VlbiBwaWVjZSBsZW5ndGggYW5kIHRvdGFsIHByYWN0aWNlIHRpbWUiKSsKICBzY2FsZV9jb2xvcl90cm9uKCkrCiAgc2NhbGVfZmlsbF90cm9uKCkrCiAgdGhlbWVfaXBzdW1fZXMoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgojIyBMZWFybmluZyBlZmZlY3QgLSBkbyBwaWVjZXMgb2YgdGhlIHNhbWUgZGlmZmljdWx0eSBiZWNvbWUgZWFzaWVyIHRvIGxlYXJuIHdpdGggdGltZT8KCldlIGNhbiBzcG90IGEgdHJlbmQgd2hlcmUgdGhlIHRpbWUgcmVxdWlyZWQgdG8gbGVhcm4gYSBwaWVjZSBvZiBhIHNpbWlsYXIgZGlmZmljdWx0eSAoQUJSU00gR3JhZGUpIGRlY3JlYXNlcyBhcyBteSBhYmlsaXR5IHRvIHBsYXkgdGhlIHBpYW5vIGluY3JlYXNlcyAoYXMganVkZ2VkIGJ5IGN1bXVsYXRpdmUgaG91cnMgb2YgcHJhY3RpY2UpLiBXZSBzaG91bGQga2VlcCB0aGlzIGluIG1pbmQgYW5kIGluY2x1ZGUgaXQgYXMgYSB2YXJpYWJsZSBpbnRvIG91ciBwcmVkaWN0aW9uIG1vZGVsLgoKYGBge3J9Cm1vZGVsX2RhdGElPiUKICAKICBnZ3Bsb3QoYWVzKEN1bXVsYXRpdmVfRHVyYXRpb24sIER1cmF0aW9uLCBncm91cCA9IDEpKSsKICBnZW9tX3BvaW50KGFlcyhjb2wgPSBMZXZlbCksIHNpemUgPSAzKSsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT1GQUxTRSkrCiAgbGFicyh4ID0gIkN1bXVsYXRpdmUgaG91cnMgcHJhY3RpY2VkIGJlZm9yZSB0aGUgZmlyc3QgcHJhY3RpY2Ugb2YgZWFjaCBwaWVjZSIsCiAgICAgICB5ID0gIkhvdXJzIG5lZWRlZCB0byBsZWFybiBhIHBpZWNlIiwKICAgICAgIHN1YnRpdGxlID0gIlBpZWNlcyBvZiBhIHNpbWlsYXIgZGlmZmljdWx0eSBiZWNvbWUgZmFzdGVyIHRvIGxlYXJuIikrCiAgc2NhbGVfY29sb3JfdHJvbigpKwogIHNjYWxlX2ZpbGxfdHJvbigpKwogIHRoZW1lX2lwc3VtX2VzKCkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKIyMgRG9lcyAicGF1c2luZyIgYSBwaWVjZSBpbXBhY3QgdGhlIHRvdGFsIHRpbWUgcmVxdWlyZWQgdG8gbGVhcm4gaXQ/CgpIb3cgZG8gd2UgZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIHBpZWNlcyB0aGF0IHdlIGxlYXJuIG9uY2UgYW5kIHRob3NlIHRoYXQgd2UgY29tZSBiYWNrIHRvIHJlcGVhdGVkbHk/IEV4YW1wbGVzIGNvdWxkIGluY2x1ZGUgd2FudGluZyB0byBpbXByb3ZlIHRoZSBwbGF5aW5nIGZ1cnRoZXIsIGxvdmluZyBpdCBzbyBtdWNoIHdlIHdhbnRlZCB0byByZWxlYXJuIGl0LCBwcmVwYXJpbmcgaXQgZm9yIGEgbmV3IHBlcmZvcm1hbmNlLCBldGMuCgpBcyBhbnlvbmUgdGhhdCBldmVyIHBsYXllZCB0aGUgcGlhbm8ga25vd3MsIHJlLWxlYXJuaW5nIGEgcGllY2UsIHBhcnRpY3VsYXJseSBhZnRlciB5b3UgImRyb3AiIGl0IGZvciBhIGZldyBtb250aHMveWVhcnMsIHJlc3VsdHMgaW4gYSBtdWNoIGJldHRlciBwZXJmb3JtYW5jZS91bmRlcnN0YW5kaW5nIG9mIHRoZSBwaWVjZS4gSSBkZWZpbml0ZWx5IGZvdW5kIHRoYXQgdG8gYmUgdHJ1ZSBpbiBteSBleHBlcmllbmNlLCBwYXJ0aWN1bGFybHkgd2l0aCBteSBleGFtIHBpZWNlcy5UaGUgZG93bnNpZGUgaXMgdGhhdCB0aGVzZSBwaWVjZXMgdGFrZSBsb25nZXIgdG8gbGVhcm4uCgpgYGB7ciB3aWR0aCA9IDEzfQptb2RlbF9kYXRhJT4lCiAgbXV0YXRlKFByb2plY3RfZm9ybWF0dGVkID0gc3RyX3JlcGxhY2VfYWxsKFByb2plY3QsIlteWzpncmFwaDpdXSIsICIgIiksCiAgICAgICAgIFByb2plY3RfbGFiZWwgPSBhcy5mYWN0b3IoaWZlbHNlKE1heF9CcmVhayA+IDMxLCBQcm9qZWN0X2Zvcm1hdHRlZCwgIiIpKSklPiUKICAKICBnZ3Bsb3QoYWVzKGFzLmludGVnZXIoTWF4X0JyZWFrKSwgRHVyYXRpb24sIGNvbCA9IE1heF9CcmVhayA8PSAzMSkpKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMpKwogIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSBQcm9qZWN0X2xhYmVsKSwgc2l6ZSA9IDMsIHNob3cubGVnZW5kID0gRkFMU0UpKwogIHNjYWxlX3hfbG9nMTAoKSsKICBzY2FsZV9jb2xvcl90cm9uKGxhYmVscyA9IGMoVFJVRSwgRkFMU0UpKSsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHJldmVyc2U9VFJVRSkpKwogIGxhYnMoeCA9ICJNYXhpbXVtIGRheXMgcGFzc2VkIGJldHdlZW4gdHdvIGNvbnNlY3V0aXZlIHNlc3Npb25zIG9uIHRoZSBzYW1lIHBpZWNlIChsb2cgc2NhbGUpIiwKICAgICAgIHkgPSAiSG91cnMgbmVlZGVkIHRvIGxlYXJuIGEgcGllY2UiLAogICAgICAgY29sID0gIkJyZWFrIChvdmVyIDEgbW9udGgpIiwKICAgICAgIHN1YnRpdGxlID0gIlRha2luZyBhIGJyZWFrIGJlZm9yZSBmaW5pc2hpbmcgYSBwaWVjZSBtaWdodCBsZWFkIHRvIG1vcmUgaG91cnMgcmVxdWlyZWQgdG8gbGVhcm4gaXQiKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCiMjIFJlcGVydG9pcmUKCmBgYHtyfQptb2RlbF9kYXRhJT4lCiAgc2VsZWN0KC1EYXlzX1ByYWN0aWNlZCwgLURhdGVfRW5kLCAtTWF4X0JyZWFrKSU+JQogIG11dGF0ZShEdXJhdGlvbiA9IHJvdW5kKER1cmF0aW9uKSwKICAgICAgICAgTGVuZ3RoID0gcm91bmQoTGVuZ3RoLCAxKSklPiUKICBhcnJhbmdlKGRlc2MoRGF0ZV9TdGFydCkpJT4lCiAgcmVuYW1lKEV4cGVyaWVuY2UgPSBDdW11bGF0aXZlX0R1cmF0aW9uLAogICAgICAgICBTdGFydGVkID0gRGF0ZV9TdGFydCklPiUKICByZWxvY2F0ZShQcm9qZWN0LCBEdXJhdGlvbiwgR2VucmUsIEFCUlNNLCBMZXZlbCwgU3RhbmRhcmQsIExlbmd0aCwgRXhwZXJpZW5jZSwgQnJlYWssIFN0YXJ0ZWQpJT4lCiAgCiAga2JsKGVzY2FwZSA9IEZBTFNFLGNhcHRpb24gPSAiUmVwZXJ0b2lyZSIpJT4lCiAga2FibGVfcGFwZXIoYygiaG92ZXIiLCAic3RyaXBlZCIpLCBmdWxsX3dpZHRoID0gRiklPiUKICBjb2x1bW5fc3BlYyhjKDEsMiksIGJvbGQgPSBULCBjb2xvciA9ICJibGFjayIpJT4lCiAgc2Nyb2xsX2JveChoZWlnaHQgPSAiNDUwcHgiKQpgYGAKCiMgTW9kZWxsaW5nCgpRdWVzdGlvbjogKipIb3cgbG9uZyB3b3VsZCBpdCB0YWtlIHRvIGxlYXJuIGEgcGllY2UgYmFzZWQgb24gdmFyaW91cyBmYWN0b3JzPyoqCgojIyBkZXRlY3Qgb3V0bGllcnMKCkdpdmVuIHRoZSB2ZXJ5IGxpbWl0ZWQgZGF0YSBhdCB0aGUgYWR2YW5jZWQgbGV2ZWwgKEdyYWRlIDcgQUJSU00pLCB0aG9zZSB0d28gcGllY2VzIHdpbGwgYmUgcmVtb3ZlZC4gT25lIGlzIGFuIGV4dHJlbWUgb3V0bGllciBhcyB3ZWxsIHdoaWNoIHdpbGwgc2lnbmlmaWNhbnRseSBpbXBhY3Qgb3VyIG1vZGVscy4KCmBgYHtyfQptb2RlbF9kYXRhIDwtIG1vZGVsX2RhdGElPiVmaWx0ZXIoQUJSU00gIT0gNyklPiVkcm9wbGV2ZWxzKCkKYGBgCgojIyBtaXNzaW5nIHZhbHVlcwoKVGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBtb2RlbGxpbmcgZGF0YXNldCBmb2xsb3dpbmcgdGhlIEVUTCBwcm9jZXNzLgoKIyMgRmVhdHVyZSBlbmdpbmVlcmluZwoKKiAqKmNhdGVnb3JpY2FsKio6CiAgKiAqKkFCUlNNIGdyYWRlKio6IDEgdG8gOAogICogKipHZW5yZSoqOiBCYXJvcXVlLCBDbGFzc2ljYWwsIFJvbWFudGljLCBNb2Rlcm4KICAqICoqQnJlYWsqKjogbGVhcm5pbmcgaXQgY29udGludW91c2x5IG9yIHNldHRpbmcgaXQgYXNpZGUgZm9yIGEgd2hpbGUgKDEgbW9udGggbWluaW11bSkKICAqICoqU3RhbmRhcmQqKiBvZiBwcmFjdGljZTogcHVibGljIHBlcmZvcm1hbmNlIG9yIGF2ZXJhZ2UgKHJlbGF0aXZlIHRvIHNvbWVvbmUncyBsZXZlbCBvZiBwbGF5aW5nKQoqICoqbnVtZXJpY2FsKio6CiAgKiAqKkV4cGVyaWVuY2UqKjogdG90YWwgaG91cnMgcHJhY3RpY2VkIGJlZm9yZSB0aGUgZmlyc3QgcHJhY3RpY2Ugc2Vzc2lvbiBvbiBlYWNoIHBpZWNlCiAgKiBwaWVjZSAqKkxlbmd0aCoqOiBtaW51dGVzCgojIyBQcmUtcHJvY2Vzc2luZwoKTGV0J3MgdXNlIHNvbWUgYmFzaWMgc3RhbmRhcmRpc2F0aW9uIG9mZmVyZWQgYnkgdGhlIGNhcmV0IHBhY2thZ2Ugc3VjaCBhcyAqKmNlbnRlcmluZyoqIChzdWJ0cmFjdCBtZWFuIGZyb20gdmFsdWVzKSBhbmQgKipzY2FsaW5nKiogKGRpdmlkZSB2YWx1ZXMgYnkgc3RhbmRhcmQgZGV2aWF0aW9uKS4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCgojIHNldCBhIGJhY2t1cCB2YXJpYWJsZQpiYWNrdXAgPC0gbW9kZWxfZGF0YQoKeSA8LSBtb2RlbF9kYXRhJER1cmF0aW9uCgojIHRha2Ugb3V0IG91ciByZXNwb25zZSB2YXJpYWJsZSB0ZW1wb3JhcmlseSBhcyB3ZSBkbyBub3Qgd2FudCB0aGlzIHRvIGJlIHByb2Nlc3NlZAptb2RlbF9kYXRhIDwtIG1vZGVsX2RhdGEgJT4lCiAgc2VsZWN0KC1EdXJhdGlvbikKCiMgY2VudGVyIGFuZCBzY2FsZSBvdXIgZGF0YSAoQm94Q294IGlmIG5lZWRlZCkKcHJlUHJvY2Vzc19yYW5nZV9tb2RlbCA8LSBwcmVQcm9jZXNzKG1vZGVsX2RhdGEsIG1ldGhvZD1jKCJjZW50ZXIiLCAic2NhbGUiKSkgCgptb2RlbF9kYXRhIDwtIHByZWRpY3QocHJlUHJvY2Vzc19yYW5nZV9tb2RlbCwgbmV3ZGF0YSA9IG1vZGVsX2RhdGEpCiAKIyBhcHBlbmQgdGhlIFkgdmFyaWFibGUgYmFjayBvbiB3aXRoIG9yaWdpbmFsIHZhbHVlcwptb2RlbF9kYXRhJER1cmF0aW9uIDwtIHkKYGBgCgojIyBSZXNhbXBsaW5nCgpHaXZlbiB0aGUgc21hbGwgc2l6ZSBvZiB0aGUgZGF0YXNldCwgYm9vdHN0cmFwcGluZyByZXNhbXBsaW5nIG1ldGhvZCB3aWxsIGJlIGFwcGxpZWQuCgpgYGB7cn0KdHJhaW4uY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImJvb3QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAyNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VhcmNoID0gInJhbmRvbSIpCmBgYAoKIyMgTW9kZWwgc2VsZWN0aW9uCgpgYGB7ciBlY2hvPVRSVUUsIGNhY2hlID0gVFJVRSwgcmVzdWx0cz0naGlkZSd9CiMgc2V0IG51bWJlciBvZiBjbHVzdGVycyAKY2x1c3RlcnMgPC0gNAoKIyBydW4gdGhlbSBhbGwgaW4gcGFyYWxsZWwKY2wgPC0gbWFrZUNsdXN0ZXIoY2x1c3RlcnMsIHR5cGUgPSAiU09DSyIpCiAKIyByZWdpc3RlciBjbHVzdGVyIHRyYWluIGluIHBhcmFsZWwKcmVnaXN0ZXJEb1NOT1coY2wpCgojIHRyYWluIG1vZGVscwptb2RlbCA8LSB0cmFpbihEdXJhdGlvbiB+IEFCUlNNICsgR2VucmUgKyBMZW5ndGggKyBDdW11bGF0aXZlX0R1cmF0aW9uICsgQnJlYWsgKyBTdGFuZGFyZCwKICAgICAgICAgICAgICAgICAgZGF0YSA9IG1vZGVsX2RhdGEsCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyYW5nZXIiLAogICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTAwLAogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbi5jb250cm9sKQoKCm1vZGVsMiA8LSB0cmFpbihEdXJhdGlvbiB+IEFCUlNNICsgR2VucmUgKyBMZW5ndGggKyBDdW11bGF0aXZlX0R1cmF0aW9uICsgQnJlYWsgKyBTdGFuZGFyZCwKICAgICAgICAgICAgICAgIGRhdGEgPSBtb2RlbF9kYXRhLAogICAgICAgICAgICAgICAgbWV0aG9kID0gImxtU3RlcEFJQyIsCiAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTAwLAogICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW4uY29udHJvbCkKCgptb2RlbDMgPC0gdHJhaW4oRHVyYXRpb24gfiBBQlJTTSArIEdlbnJlICsgTGVuZ3RoICsgQ3VtdWxhdGl2ZV9EdXJhdGlvbiArIEJyZWFrICsgU3RhbmRhcmQsCiAgICAgICAgICAgICAgICBkYXRhID0gbW9kZWxfZGF0YSwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJsbSIsCiAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTAwLAogICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW4uY29udHJvbCkKCm1vZGVsNCA8LSB0cmFpbihEdXJhdGlvbiB+IEFCUlNNICsgR2VucmUgKyBMZW5ndGggKyBDdW11bGF0aXZlX0R1cmF0aW9uICsgQnJlYWsgKyBTdGFuZGFyZCwKICAgICAgICAgICAgICAgIGRhdGEgPSBtb2RlbF9kYXRhLAogICAgICAgICAgICAgICAgbWV0aG9kID0gInJpZGdlIiwKICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMDAsCiAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbi5jb250cm9sKQoKbW9kZWw1IDwtIHRyYWluKER1cmF0aW9uIH4gQUJSU00gKyBHZW5yZSArIExlbmd0aCArIEN1bXVsYXRpdmVfRHVyYXRpb24gKyBCcmVhayArIFN0YW5kYXJkLAogICAgICAgICAgICAgICAgZGF0YSA9IG1vZGVsX2RhdGEsCiAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLAogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDEwMCwKICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluLmNvbnRyb2wpCgptb2RlbDYgPC0gdHJhaW4oRHVyYXRpb24gfiBBQlJTTSArIEdlbnJlICsgTGVuZ3RoICsgQ3VtdWxhdGl2ZV9EdXJhdGlvbiArIEJyZWFrICsgU3RhbmRhcmQsCiAgICAgICAgICAgICAgICBkYXRhID0gbW9kZWxfZGF0YSwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnYm0iLAogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDEwMCwKICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluLmNvbnRyb2wpCgptb2RlbDcgPC0gdHJhaW4oRHVyYXRpb24gfiBBQlJTTSArIEdlbnJlICsgTGVuZ3RoICsgQ3VtdWxhdGl2ZV9EdXJhdGlvbiArIEJyZWFrICsgU3RhbmRhcmQsCiAgICAgICAgICAgICAgICBkYXRhID0gbW9kZWxfZGF0YSwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJwbHMiLAogICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDEwMCwKICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluLmNvbnRyb2wpCiAKIyBzaHV0IHRoZSBpbnN0YW5jZXMgb2YgUiBkb3duCnN0b3BDbHVzdGVyKGNsKQoKIyBjb21wYXJlIG1vZGVscwptb2RlbF9saXN0IDwtIGxpc3QocmFuZ2VyID0gbW9kZWwsIGxtU3RlcEFJQyA9IG1vZGVsMiwgbG0gPSBtb2RlbDMsIHJpZGdlID0gbW9kZWw0LCByZiA9IG1vZGVsNSwgZ2JtID0gbW9kZWw2LCBwbHMgPSBtb2RlbDcpCgptb2RlbF9jb21wYXJpc29uIDwtIHJlc2FtcGxlcyhtb2RlbF9saXN0KQoKIyBsZWFybmluZyBjdXJ2ZXMgdG8gaW5kaWNhdGUgb3ZlcmZpdHRpbmcgYW5kIHVuZGVyZml0dGluZwojIGh5cGVyIHBhcmFtZXRlcnMgCiMgaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L21vZGVsLXRyYWluaW5nLWFuZC10dW5pbmcuaHRtbCNtb2RlbC10cmFpbmluZy1hbmQtcGFyYW1ldGVyLXR1bmluZwojIGh0dHBzOi8vdG9wZXBvLmdpdGh1Yi5pby9jYXJldC9yYW5kb20taHlwZXJwYXJhbWV0ZXItc2VhcmNoLmh0bWwKYGBgCgpXZSBjaG9zZSB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbCBhcyBpdCB3YXMgdGhlIGJlc3QgcGVyZm9ybWluZyBtb2RlbC4gSXQgaXMga25vd24gYXMgYSBtb2RlbCB3aGljaCBpczoKCiogbm90IHZlcnkgc2Vuc2l0aXZlIHRvIG91dGxpZXJzCiogZ29vZCBmb3Igbm9uLWxpbmVhcml0eQoqIHZhcmlhYmxlIGltcG9ydGFuY2UgY2FuIGJlIGJpYXNlZCBpZiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaGF2ZSBmZXcgbGV2ZWxzICh0b3dhcmQgaGlnaCBsZXZlbHMpIG9yIGFyZSBjb3JyZWxhdGVkCgpgYGB7cn0Kc3VtbWFyeShtb2RlbF9jb21wYXJpc29uKQpgYGAKCkJhc2VkIG9uIG91ciByZWdyZXNzaW9uIG1vZGVsLCBpdCBkb2VzIG5vdCBsb29rIGxpa2Ugd2UgaGF2ZSBzaWduaWZpY2FudCBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSBmdWxsIG1vZGVsIHZhcmlhYmxlcyBzbyB3ZSBjYW4gY29udGludWUgYXMgaXQgaXMuCgpgYGB7cn0KdGlkeSh2aWYobW9kZWwzJGZpbmFsTW9kZWwpKSU+JQogIHJlbmFtZShWSUYgPSB4KSU+JQogIG11dGF0ZShWSUYgPSByb3VuZChWSUYsIDEpKSU+JQogIGFycmFuZ2UoZGVzYyhWSUYpKSU+JQogIGtibChjYXB0aW9uID0gIlZhcmlhbmNlIEluZmxhdGlvbiBGYWN0b3IgKFZJRikiKSU+JQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQpgYGAKCiMjIyBBY3R1YWxzIHZzIFByZWRpY3Rpb25zCgpgYGB7cn0Kc2VsZWN0ZWRfbW9kZWwgPC0gbW9kZWw1CgojU2F2aW5nIHRoZSBtb2RlbApzYXZlUkRTKHNlbGVjdGVkX21vZGVsLCBmaWxlID0gIm1vZGVsLnJkYSIpCgojZ2V0IHByZWRpY3Rpb25zCnByZWRpY3Rpb25zIDwtIHByZWRpY3Qoc2VsZWN0ZWRfbW9kZWwsIG1vZGVsX2RhdGEpCgojY3JlYXRlIGRhdGFzZXQKbW9kZWxfZGF0YTIgPC0gbW9kZWxfZGF0YQptb2RlbF9kYXRhMiRQcmVkaWN0ZWQgPC0gcHJlZGljdGlvbnMKbW9kZWxfZGF0YTIkQWN0dWFsIDwtIG1vZGVsX2RhdGEkRHVyYXRpb24KbW9kZWxfZGF0YTIkUmVzaWR1YWxzIDwtIG1vZGVsX2RhdGEyJEFjdHVhbCAtIG1vZGVsX2RhdGEyJFByZWRpY3RlZAoKIyBtb2RlbF9kYXRhMiA8LSBtb2RlbF9kYXRhJT4lCiMgICBtdXRhdGUoQWN0dWFsID0gYXMubnVtZXJpYyhEdXJhdGlvbiksCiMgICAgICAgICAgUHJlZGljdGVkID0gYXMubnVtZXJpYyhwcmVkaWN0aW9ucyksCiMgICAgICAgICAgUmVzaWR1YWxzID0gQWN0dWFsIC0gUHJlZGljdGVkKSU+JQojICAgc2VsZWN0KFByZWRpY3RlZCwgQWN0dWFsLCBSZXNpZHVhbHMsIFByb2plY3QsIExldmVsLCBHZW5yZSkKCiN2aXN1YWxpc2UgcHJlZGljdGVkIHZzIGFjdHVhbApnZ3Bsb3RseSgKZ2dwbG90KG1vZGVsX2RhdGEyLCBhZXMoUHJlZGljdGVkLCBBY3R1YWwsIGxhYmVsID0gUmVzaWR1YWxzLCBjb2wgPSBMZXZlbCkpKwogIGdlb21fcG9pbnQoYWVzKHRleHQgPSBQcm9qZWN0KSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSkrCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sID0gInJlZCIsIGx3ZCA9IDEsIHNlID0gRkFMU0UpKwogIGdlb21fYWJsaW5lKGx0eSA9ICJkYXNoZWQiLCBsd2QgPSAwLjUsIGNvbCA9ICJncmF5IikrCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsNTApLCB5bGltID0gYygwLDUwKSkrCiAgbGFicyhjb2wgPSBOVUxMKSsKICBzY2FsZV9jb2xvcl90cm9uKCkrCiAgdGhlbWVfaXBzdW1fZXMoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCikgJT4lCiAgbGF5b3V0KGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjQsIHkgPSAxLjIpKQpgYGAKCiMjIyBSZXNpZHVhbCBkaXN0cmlidXRpb24KCldlIGNhbiBzZWUgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBtb3N0bHkgc2l0dWF0ZWQgYXJvdW5kIDAuCgpgYGB7cn0KZ2dwbG90KG1vZGVsX2RhdGEyLCBhZXMoUmVzaWR1YWxzLCBmaWxsID0gLi5jb3VudC4uKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBjb2wgPSAiYmxhY2siKSsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4oUmVzaWR1YWxzKSksIGx3ZCA9IDEsIGx0eSA9IDIpICsKICBsYWJzKHg9IlJlc2lkdWFscyIsCiAgICAgICB5PSAiVG90YWwgb2NjdXJlbmNlcyIpKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ5ZWxsb3ciLCBoaWdoPSJyZWQiKSsKICB0aGVtZV9pcHN1bV9lcygpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgQWN0dWFscyB2ZXJzdXMgUmVzaWR1YWxzCgpMb29raW5nIGF0IHRoZSB2YXJpYWJpbGl0eSBvZiBlcnJvcnMsIHRoZXJlIGlzIHN0aWxsIGEgdGVuZGVuY3kgdG8gb3Zlci1wcmVkaWN0IGZvciBwaWVjZXMgdGhhdCB0b29rIHZlcnkgbGl0dGxlIGFuZCB1bmRlci1wcmVkaWN0IGZvciB0aGUgbW9yZSBkaWZmaWN1bHQgb25lcy4gVGhlcmUgY291bGQgYmUgdHdvIG1haW4gcmVhc29ucyBmb3IgdGhpczoKCiogcHJhY3RpY2luZyBhbiBvbGQgcGllY2UgaW4gb3JkZXIgdG8gZnVydGhlciBpbXByb3ZlICh3aGljaCBuYXR1cmFsbHkgYWRkcyBtb3JlIHByYWN0aWNlIHRpbWUgYXMgSSByZS1sZWFybiBpdCkKKiBsZWFybmluZyBlYXNpZXIgcGllY2VzIGxhdGVyIG9uIGluIG15IGpvdXJuZXkgd2hpY2ggbWVhbnMgSSB3aWxsIGxlYXJuIHRoZW0gZmFzdGVyIHRoYW4gZXhwZWN0ZWQgKGJhc2VkIG9uIG15IGVhcmxpZXIgZGF0YSB3aGVyZSBhIHBpZWNlIG9mIGEgc2ltaWxhciBkaWZmaWN1bHR5IHRvb2sgbG9uZ2VyKQoKYGBge3J9CmdncGxvdGx5KApnZ3Bsb3QobW9kZWxfZGF0YTIsIGFlcyhBY3R1YWwsIFJlc2lkdWFscywgY29sID0gTGV2ZWwsIGxhYmVsID0gUHJlZGljdGVkKSkrCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDMsIGNvbG9yID0gImdyZXk1MiIpKwogIGdlb21fcG9pbnQoYWVzKHRleHQgPSBQcm9qZWN0KSwgYWxwaGEgPSAwLjc1LCBzaXplID0gMykrCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sID0gInJlZCIsIHNlID0gRkFMU0UpKwogIGxhYnMoY29sID0gTlVMTCkrCiAgc2NhbGVfY29sb3JfdHJvbigpKwogIHRoZW1lX2lwc3VtX2VzKCkKKSAlPiUKICBsYXlvdXQobGVnZW5kID0gbGlzdChvcmllbnRhdGlvbiA9ICJoIix4ID0gMC40LCB5ID0gMS4yKSkKYGBgCgojIyBMaW5lYXIgUmVncmVzc2lvbiAoTFIpIG9yIFJhbmRvbSBGb3Jlc3QgKFJGKT8KCldlIGNhbiBzZWUgdGhhdCB0aGUgUmFuZG9tIEZvcmVzdCBwZXJmb3JtZWQgc2lnbmlmaWNhbnRseSBiZXR0ZXIgdGhhbiB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwuIFRoaXMgaXNuJ3Qgc3VycHJpc2luZyBzaW5jZSB0aGVyZSBtaWdodCBiZSBub24tbGluZWFyIHRyZW5kcyB3aXRoaW4gdGhlIGRhdGEsIGFuZCBSRnMgYXJlIGtub3duIHRvIGJlIG1vcmUgYWNjdXJhdGUuCgpgYGB7cn0KdGlkeShjb21wYXJlX21vZGVscyhtb2RlbDMsIG1vZGVsNSkpJT4lCiAga2JsKGNhcHRpb24gPSAiTW9kZWwgMSB2cyBtb2RlbCAyIiklPiUKICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikKYGBgCgojIyBIb3cgbWFueSBwcmVkaWN0b3JzIGRpZCB0aGUgbW9zdCBvcHRpbWFsIG1vZGVsIGhhdmU/CgpgYGB7ciBwcmVkaWN0b3JzfQpwbG90KG1vZGVsNSwgbWFpbiA9ICJUaGUgbW9zdCBvcHRpbWFsIG1vZGVsIHdhcyB0aGF0IHdpdGggNiBwcmVkaWN0b3JzIiwgY29sID0gIm9yYW5nZSIsIGx3ZCA9IDEuNSkKYGBgCgojIyBXaGF0IHdlcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcz8KCldlIGNhbiBub3cgc2VlIHRoYXQgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBzZWVtZWQgdG8gYmUgdGhlIGxlbmd0aCBvZiB0aGUgcGllY2UsIG15IGV4cGVyaWVuY2UgcHJpb3IgdG8gc3RhcnRpbmcgYSBwaWVjZSBhbmQgdGltZSBkaWZmaWN1bHR5IG9mIHRoZSBwaWVjZS4gVGhlc2Ugd2VyZSBhbHNvIGNvbmZpcm1lZCBieSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuCgpgYGB7ciBmYWN0b3JzfQppbXAgPC0gYXMubWF0cml4KHZhckltcChtb2RlbDUpJGltcG9ydGFuY2UpJT4lCiAgYXMuZGF0YS5mcmFtZSgpJT4lCiAgcmVuYW1lKEltcG9ydGFuY2UgPSBPdmVyYWxsKSU+JQogIG11dGF0ZShGZWF0dXJlID0gYXMuZmFjdG9yKHJvd25hbWVzKC4pKSwKICAgICAgICAgRmVhdHVyZSA9IHJlb3JkZXIoRmVhdHVyZSwgSW1wb3J0YW5jZSkpCgpnZ3Bsb3QoaW1wLCBhZXMoRmVhdHVyZSwgSW1wb3J0YW5jZSkpKwogIGdlb21fc2VnbWVudChhZXMoRmVhdHVyZSwgeSA9IDAsIHhlbmQgPSBGZWF0dXJlLCB5ZW5kID0gSW1wb3J0YW5jZSksIGNvbCA9ICJibGFjayIsIHNpemUgPSAxLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAxMCwgY29sID0gIm9yYW5nZSIpKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShyb3VuZChJbXBvcnRhbmNlKSwgIiUiLCBzZXAgPSAiIikpLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAzLCBjaGVja19vdmVybGFwID0gVFJVRSkrCiAgc2NhbGVfY29sb3JfdHJvbigpKwogIHNjYWxlX2ZpbGxfdHJvbigpKwogIHRoZW1lX2lwc3VtX2VzKCkrCiAgY29vcmRfZmxpcCgpKwogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgaW1wb3J0YW5jZSByYW5raW5nIikrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSAgZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKQoKI3Bsb3QodmFySW1wKG1vZGVsNSkpCmBgYAoKIyBMaW1pdGF0aW9ucwoKKiB2ZXJ5ICoqbGltaXRlZCBkYXRhKiogd2hpY2ggZGlkIG5vdCBhbGxvdyBmb3IgYSB0cmFpbi90ZXN0IHNwbGl0OyBob3dldmVyLCBhIGJvb3RzdHJhcCByZXNhbXBsaW5nIG1ldGhvZCBpcyBrbm93biB0byBiZSBhIGdvb2Qgc3Vic3RpdHV0ZQoqIGJpYXNlZCB0byAqKm9uZSBwZXJzb24ncyoqIGxlYXJuaW5nIGFiaWxpdHkgKG90aGVycyBtaWdodCBsZWFybiBmYXN0ZXIgb3Igc2xvd2VyKQoqIG9uIHRvcCBvZiB0b3RhbCBob3VycyBvZiBwcmFjdGljZSwgKipxdWFsaXR5IG9mIHByYWN0aWNlKiogaXMgYSBzaWduaWZpY2FudCBmYWN0b3Igd2hpY2ggaXMgbm90IGNhcHR1cmVkIGluIHRoaXMgZGF0YXNldAoqIHZlcnkgKipkaWZmaWN1bHQgdG8gYXNzZXNzIHdoZW4gYSBwaWVjZSBpcyAiZmluaXNoZWQiKiogYXMgeW91IGNhbiBhbHdheXMgZnVydGhlciBpbXByb3ZlIG9uIHlvdXIgaW50ZXJwcmV0YXRpb24KKiBub3QgYWxsIHBpZWNlcyBoYWQgb2ZmaWNpYWwgKipBQlJTTSByYXRpbmdzKiogYW5kIGEgZmV3IGhhZCB0byBiZSBlc3RpbWF0ZWQ7IGV2ZW4gZm9yIHRob3NlIHRoYXQgZG8gaGF2ZSBhbiBvZmZpY2lhbCByYXRpbmcsIHRoZSBkaWZmaWN1bHR5IG9mIGEgcGllY2UgaXMgaGlnaGx5IHN1YmplY3RpdmUgdG8gZWFjaCBwaWFuaXN0IGFuZCBoYXJkIHRvIHF1YW50aWZ5IHdpdGggb25lIG51bWJlcgoqICoqbWVtb3Jpc2F0aW9uKiogbWlnaHQgYmUgYSBjb25mb3VuZGluZyB2YXJpYWJsZSB0aGF0IHdhcyBub3QgYWNjb3VudGVkIGZvcjsgc29tZXRpbWVzIHRoZXJlJ3MgYW4gZWZmb3J0IHRvIHByYWN0aWNlIGEgYml0IGZvciBsb25nZXIganVzdCB0byBtZW1vcmlzZSB3aXRob3V0IGFuIGltcHJvdmVtZW50IGluIHBlcmZvcm1hbmNlCgojIEhhcmRlc3QgdGhpbmdzIGFib3V0IHRoaXMgYW5hbHlzaXM6CgoqIHRoZSBFeHRyYWN0LVRyYW5zZm9ybS1Mb2FkIHByb2Nlc3MgLSBjbGVhbiB0aGUgImRpcnR5IGRhdGEiIGFuZCBmaW5kIGNyZWF0aXZlIHdheXMgdG8gaW5wdXQgdGhlIGRhdGEgb24gdGhlIGZyb250IGVuZCBvZiB0aGUgYXBwIHRvIG1ha2UgaXQgcmVwb3J0aW5nIGZyaWVuZGx5IG9uIHRoZSBiYWNrLWVuZCAod2l0aCBhbGwgdGhlIHZhcmlhYmxlcyBzdWNoIGFzIEdlbnJlLCBUeXBlIG9mIHByYWN0aWNlLCBDb21wb3NlciBhbmQgUGllY2UgbmFtZSwgdGFnIHBpZWNlcyBhcyAid29yayBpbiBwcm9ncmVzcyIgZXRjKQoqIGF1dG9tYXRlIHdheXMgdG8gZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIHBpZWNlcyB0aGF0IEkgY2FtZSBiYWNrIHRvIHZzIHBpZWNlcyBJIG9ubHkgc3R1ZGllZCBvbmNlCiogd29yayB3aXRoIHZlcnkgbGltaXRlZCBkYXRhCgojIEludGVyYWN0aXZlIGFwcGxpY2F0aW9uOgoKKiB5b3UgY2FuIGZpbmQgYW4gaW50ZXJhY3RpdmUgZGlzcGxheSBvZiB0aGlzIHByZXNlbnRhdGlvbiwgYXMgd2VsbCBhcyB0aGUgbW9kZWwgaW4gcHJvZHVjdGlvbiBhdCB0aGUgW2ZvbGxvd2luZyBsaW5rXShodHRwczovL3BldGVyaG9udGFydS5zaGlueWFwcHMuaW8vcGlhbm8tcHJhY3RpY2UtcHJlZGljdGlvbi8pCiogaHR0cHM6Ly9wZXRlcmhvbnRhcnUuc2hpbnlhcHBzLmlvL3BpYW5vLXByYWN0aWNlLXByZWRpY3Rpb24vCgojIFdoYXQncyBuZXh0PwoKKiBrZWVwIHByYWN0aWNpbmcsIGdhdGhlciBtb3JlIGRhdGEgYW5kIHJlZnJlc2ggdGhpcyBhbmFseXNpcyArIHVwZGF0ZSB0aGUgbW9kZWwKKiBhZGQgYSByZWNvbW1lbmRlciB0YWIgdG8gdGhlIHNoaW55IGRhc2hib2FyZCB3aGVyZSBwZW9wbGUgY291bGQgYmUgcmVjb21tZW5kZWQgYSBwaWVjZSBiYXNlZCBvbiBzcGVjaWZpYyBmZWF0dXJlcwoqIGNvbm5lY3QgdG8gdGhlIFRvZ2dsIEFQSSBmb3IgbGl2ZSB1cGRhdGVz